From ff7d49091c9fa62bcb5e93ae85c4c69f1bce2312 Mon Sep 17 00:00:00 2001 From: billy1624 Date: Fri, 2 Feb 2024 09:00:18 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20SeaQL/se?= =?UTF-8?q?aql.github.io@dde3bd579fdbc1ad6594bf6d092a9551fc2bde8f=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- blog/2021-07-01-welcome/index.html | 4 ++-- blog/2021-08-07-hello-world/index.html | 4 ++-- blog/2021-08-30-release-model/index.html | 4 ++-- blog/2021-09-20-introducing-sea-orm/index.html | 4 ++-- blog/2021-10-01-whats-new-in-0.2.4/index.html | 4 ++-- blog/2021-10-15-whats-new-in-0.3.0/index.html | 4 ++-- blog/2021-11-19-whats-new-in-0.4.0/index.html | 4 ++-- blog/2022-01-01-whats-new-in-0.5.0/index.html | 4 ++-- blog/2022-02-07-whats-new-in-0.6.0/index.html | 4 ++-- blog/2022-03-26-whats-new-in-0.7.0/index.html | 4 ++-- blog/2022-04-04-introducing-starfish-ql/index.html | 4 ++-- blog/2022-05-14-faq-01/index.html | 4 ++-- blog/2022-05-15-whats-new-in-0.8.0/index.html | 4 ++-- blog/2022-06-02-summer-of-code-2022-intro/index.html | 4 ++-- blog/2022-07-17-whats-new-in-0.9.0/index.html | 4 ++-- blog/2022-07-30-engineering/index.html | 4 ++-- blog/2022-08-05-whats-new-in-seaquery-0.26.0/index.html | 4 ++-- blog/2022-08-12-3k-github-stars/index.html | 4 ++-- blog/2022-09-17-introducing-seaography/index.html | 4 ++-- blog/2022-09-27-getting-started-with-seaography/index.html | 4 ++-- blog/2022-10-31-whats-new-in-seaquery-0.27.0/index.html | 4 ++-- .../index.html | 4 ++-- blog/2022-11-10-whats-new-in-0.10.x/index.html | 4 ++-- blog/2022-12-02-whats-new-in-seaography-0.3.0/index.html | 4 ++-- blog/2022-12-30-whats-new-in-seaquery-0.28.0/index.html | 4 ++-- .../2023-01-01-call-for-contributors-n-reviewers/index.html | 4 ++-- blog/2023-01-28-internship-at-seaql/index.html | 4 ++-- blog/2023-02-05-faq-02/index.html | 4 ++-- blog/2023-02-08-whats-new-in-seaorm-0.11.0/index.html | 4 ++-- blog/2023-04-03-intro-sea-streamer/index.html | 4 ++-- blog/2023-08-12-announcing-seaorm-0.12/index.html | 4 ++-- blog/2023-09-06-whats-new-in-sea-streamer-0.3/index.html | 4 ++-- blog/2023-11-22-async-runtime-generic/index.html | 4 ++-- blog/2023-11-25-openuk-award/index.html | 4 ++-- blog/2024-01-18-community-survey-2023/index.html | 4 ++-- blog/2024-01-23-whats-new-in-seaorm-0.12.x/index.html | 6 +++--- blog/404.html | 4 ++-- blog/about-us/index.html | 4 ++-- blog/archive/index.html | 4 ++-- blog/assets/js/97592684.a164a4d8.js | 1 + blog/assets/js/b2f554cd.93bd73b0.js | 1 + blog/assets/js/ce29dbb1.eee58e3c.js | 1 + blog/assets/js/runtime~main.830afb7b.js | 1 + blog/atom.xml | 2 +- blog/gsoc-2022/index.html | 4 ++-- blog/index.html | 6 +++--- blog/rss.xml | 2 +- blog/search/index.html | 4 ++-- blog/tags/index.html | 4 ++-- blog/tags/news/index.html | 6 +++--- 50 files changed, 97 insertions(+), 93 deletions(-) create mode 100644 blog/assets/js/97592684.a164a4d8.js create mode 100644 blog/assets/js/b2f554cd.93bd73b0.js create mode 100644 blog/assets/js/ce29dbb1.eee58e3c.js create mode 100644 blog/assets/js/runtime~main.830afb7b.js diff --git a/blog/2021-07-01-welcome/index.html b/blog/2021-07-01-welcome/index.html index c12088ec550..f3fd12db8e5 100644 --- a/blog/2021-07-01-welcome/index.html +++ b/blog/2021-07-01-welcome/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Welcome to SeaQL

ยท One min read
Chris Tsang

One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren't many choices. So we have to write our own.

December last year, we released SeaQuery, and received welcoming responses from the community. We decided to push the project further and develop a full blown async ORM.

It has been a bumpy ride, as designing an async ORM requires working within and sometimes around Rust's unique type system. After several iterations of experimentation, I think we've attained a balance between static & dynamic and compile-time & run-time that it offers benefits of the Rust language while still be familiar and easy-to-work-with for those who come from other languages.

SeaORM is tentative to be released in Sep 2021 and stabilize in May 2022. We hope that SeaORM will become a go-to choice for working with databases in Rust and that the Rust language will be adopted by more organizations in building applications.

If you are intrigued like I do, please stay in touch and join the community.

Share your thoughts here.

- + \ No newline at end of file diff --git a/blog/2021-08-07-hello-world/index.html b/blog/2021-08-07-hello-world/index.html index aa0594c6a61..af5721c19e4 100644 --- a/blog/2021-08-07-hello-world/index.html +++ b/blog/2021-08-07-hello-world/index.html @@ -10,13 +10,13 @@ - +
Skip to main content
- + \ No newline at end of file diff --git a/blog/2021-08-30-release-model/index.html b/blog/2021-08-30-release-model/index.html index 18e05d5d1d7..e30702c9b25 100644 --- a/blog/2021-08-30-release-model/index.html +++ b/blog/2021-08-30-release-model/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Release Model

ยท One min read
Chris Tsang

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.

- + \ No newline at end of file diff --git a/blog/2021-09-20-introducing-sea-orm/index.html b/blog/2021-09-20-introducing-sea-orm/index.html index 982fe41ec74..e830eba1d4a 100644 --- a/blog/2021-09-20-introducing-sea-orm/index.html +++ b/blog/2021-09-20-introducing-sea-orm/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Introducing SeaORM ๐Ÿš

ยท 5 min read
Chris Tsang

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
- + \ No newline at end of file diff --git a/blog/2021-10-01-whats-new-in-0.2.4/index.html b/blog/2021-10-01-whats-new-in-0.2.4/index.html index 7fd69003637..be2cc451688 100644 --- a/blog/2021-10-01-whats-new-in-0.2.4/index.html +++ b/blog/2021-10-01-whats-new-in-0.2.4/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.2.4

ยท 2 min read
SeaQL Team

๐ŸŽ‰ 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.

- + \ No newline at end of file diff --git a/blog/2021-10-15-whats-new-in-0.3.0/index.html b/blog/2021-10-15-whats-new-in-0.3.0/index.html index c0c76dc5574..6e174afe2c1 100644 --- a/blog/2021-10-15-whats-new-in-0.3.0/index.html +++ b/blog/2021-10-15-whats-new-in-0.3.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.3.0

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

- + \ No newline at end of file diff --git a/blog/2021-11-19-whats-new-in-0.4.0/index.html b/blog/2021-11-19-whats-new-in-0.4.0/index.html index 782a410ea62..7c27186bd27 100644 --- a/blog/2021-11-19-whats-new-in-0.4.0/index.html +++ b/blog/2021-11-19-whats-new-in-0.4.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.4.0

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

- + \ No newline at end of file diff --git a/blog/2022-01-01-whats-new-in-0.5.0/index.html b/blog/2022-01-01-whats-new-in-0.5.0/index.html index 463bd73242a..0a1d0ae8b74 100644 --- a/blog/2022-01-01-whats-new-in-0.5.0/index.html +++ b/blog/2022-01-01-whats-new-in-0.5.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.5.0

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

- + \ No newline at end of file diff --git a/blog/2022-02-07-whats-new-in-0.6.0/index.html b/blog/2022-02-07-whats-new-in-0.6.0/index.html index d31c1037479..aa6923a41c0 100644 --- a/blog/2022-02-07-whats-new-in-0.6.0/index.html +++ b/blog/2022-02-07-whats-new-in-0.6.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.6.0

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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.

- + \ No newline at end of file diff --git a/blog/2022-03-26-whats-new-in-0.7.0/index.html b/blog/2022-03-26-whats-new-in-0.7.0/index.html index 7b02d83b9fe..e2e1de3ea7e 100644 --- a/blog/2022-03-26-whats-new-in-0.7.0/index.html +++ b/blog/2022-03-26-whats-new-in-0.7.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.7.0

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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!

- + \ No newline at end of file diff --git a/blog/2022-04-04-introducing-starfish-ql/index.html b/blog/2022-04-04-introducing-starfish-ql/index.html index 55e3ea67930..8182aba7c60 100644 --- a/blog/2022-04-04-introducing-starfish-ql/index.html +++ b/blog/2022-04-04-introducing-starfish-ql/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Introducing StarfishQL

ยท 8 min read
SeaQL Team

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 thatserde 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.ย a,ย a-,ย a0,ย  ...,ย zyx_test,ย zz-buffer,ย z_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.ย a,ย a-,ย a-bot,ย  ...,ย zzp-tools,ย zzz,ย z_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.

- + \ No newline at end of file diff --git a/blog/2022-05-14-faq-01/index.html b/blog/2022-05-14-faq-01/index.html index e90f4b2d442..f3093aac271 100644 --- a/blog/2022-05-14-faq-01/index.html +++ b/blog/2022-05-14-faq-01/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

SeaORM FAQ.01

ยท 2 min read
Chris Tsang

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.

- + \ No newline at end of file diff --git a/blog/2022-05-15-whats-new-in-0.8.0/index.html b/blog/2022-05-15-whats-new-in-0.8.0/index.html index 9b37a78b2f4..9c145f17958 100644 --- a/blog/2022-05-15-whats-new-in-0.8.0/index.html +++ b/blog/2022-05-15-whats-new-in-0.8.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.8.0

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.8.0 today! Here are some feature highlights ๐ŸŒŸ:

Migration Utilities Moved to sea-orm-migration crateโ€‹

[#666] Utilities of SeaORM migration have been moved from sea-schema to sea-orm-migration crate. Users are advised to upgrade from older versions with the following steps:

  1. Bump sea-orm version to 0.8.0.

  2. Replace sea-schema dependency with sea-orm-migration in your migration crate.

    migration/Cargo.toml
    [dependencies]
    - sea-schema = { version = "^0.7.0", ... }
    + sea-orm-migration = { version = "^0.8.0" }
  3. Find and replace use sea_schema::migration:: with use sea_orm_migration:: in your migration crate.

    - use sea_schema::migration::prelude::*;
    + use sea_orm_migration::prelude::*;

    - use sea_schema::migration::*;
    + use sea_orm_migration::*;
Designed by:

Chris Tsang
Contributed by:

Billy Chan

Generating New Migrationโ€‹

[#656] You can create a new migration with the migrate generate subcommand. This simplifies the migration process, as new migrations no longer need to be added manually.

# A migration file `MIGRATION_DIR/src/mYYYYMMDD_HHMMSS_create_product_table.rs` will be created.
# And, the migration file will be imported and included in the migrator located at `MIGRATION_DIR/src/lib.rs`.
sea-orm-cli migrate generate create_product_table
Proposed & Contributed by:

Viktor Bahr

Inserting One with Defaultโ€‹

[#589] Insert a row populate with default values. Note that the target table should have default values defined for all of its columns.

let pear = fruit::ActiveModel {
..Default::default() // all attributes are `NotSet`
};

// The SQL statement:
// - MySQL: INSERT INTO `fruit` VALUES ()
// - SQLite: INSERT INTO "fruit" DEFAULT VALUES
// - PostgreSQL: INSERT INTO "fruit" VALUES (DEFAULT) RETURNING "id", "name", "cake_id"
let pear: fruit::Model = pear.insert(db).await?;
Proposed by:

Crypto-Virus
Contributed by:

Billy Chan

Checking if an ActiveModel is changedโ€‹

[#683] You can check whether any field in an ActiveModel is Set with the help of the is_changed method.

let mut fruit: fruit::ActiveModel = Default::default();
assert!(!fruit.is_changed());

fruit.set(fruit::Column::Name, "apple".into());
assert!(fruit.is_changed());
Proposed by:

Karol Fuksiewicz
Contributed by:

Kirawi

Minor Improvementsโ€‹

  • [#670] Add max_connections option to sea-orm-cli generate entity subcommand
  • [#677] Derive Eq and Clone for DbErr
Proposed & Contributed by:

benluelo

Sebastien Guillemot

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. It can be integrated easily with common RESTful frameworks and also gRPC frameworks; check out our new Tonic example to see how it works. More examples wanted!

Who's using SeaORM?โ€‹

The following products are powered by SeaORM:



A lightweight web security auditing toolkit

The enterprise ready webhooks service

A personal search engine

SeaORM is the foundation of StarfishQL, an experimental graph database and query engine.

For more projects, see Built with SeaORM.

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.9.x.

GSoC 2022โ€‹

We are super excited to be selected as a Google Summer of Code 2022 mentor organization. The application is now closed, but the program is about to start! If you have thoughts over how we are going to implement the project ideas, feel free to participate in the discussion.

- + \ No newline at end of file diff --git a/blog/2022-06-02-summer-of-code-2022-intro/index.html b/blog/2022-06-02-summer-of-code-2022-intro/index.html index d2e29d81bc4..1275fe5e470 100644 --- a/blog/2022-06-02-summer-of-code-2022-intro/index.html +++ b/blog/2022-06-02-summer-of-code-2022-intro/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Welcome Summer of Code 2022 Contributors

ยท 4 min read
SeaQL Team

We are thrilled to announce that we will bring in four contributors this summer! Two of them are sponsored by Google while two of them are sponsored by SeaQL.

A GraphQL Framework on Top of SeaORMโ€‹

Panagiotis Karatakis

I'm Panagiotis, I live in Athens Greece and currently I pursue my second bachelors on economic sciences. My first bachelors was on computer science and I've a great passion on studying and implementing enterprise software solutions. I know Rust the last year and I used it almost daily for a small startup project that me and my friends build for a startup competition.

I'll be working on creating a CLI tool that will explore a database schema and then generate a ready to build async-graphql API. The tool will allow quick integration with the SeaQL and Rust ecosystems as well as GraphQL. To be more specific, for database exploring I'll use sea-schema and sea-orm-codegen for entity generation, my job is to glue those together with async-graphql library. You can read more here.

SQL Interpreter for Mock Testingโ€‹

Samyak Sarnayak

I'm Samyak Sarnayak, a final year Computer Science student from Bangalore, India. I started learning Rust around 6-7 months ago and it feels like I have found the perfect language for me :D. It does not have a runtime, has a great type system, really good compiler errors, good tooling, some functional programming patterns and metaprogramming. You can find more about me on my GitHub profile.

I'll be working on a new SQL interpreter for mock testing. This will be built specifically for testing and so the emphasis will be on correctness - it can be slow but the operations must always be correct. I'm hoping to build a working version of this and integrate it into the existing tests of SeaORM. Here is the discussion for this project.

Support TiDB in the SeaQL Ecosystemโ€‹

Edit: This project was canceled.

Query Linter for SeaORMโ€‹

Edit: This project was canceled.

Mentorsโ€‹

Chris Tsang

I am a strong believer in open source. I started my GitHub journey 10 years ago, when I published my first programming library. I had been looking for a programming language with speed, ergonomic and expressiveness. Until I found Rust.

Seeing a niche and demand for data engineering tools in the Rust ecosystem, I founded SeaQL in 2020 and have been leading the development and maintaining the libraries since then.


Billy Chan

Hey, this is Billy from Hong Kong. I've been using open-source libraries ever since I started coding but it's until 2020, I dedicated myself to be a Rust open-source developer.

I was also a full-stack developer specialized in formulating requirement specifications for user interfaces and database structures, implementing and testing both frontend and backend from ground up, finally releasing the MVP for production and maintaining it for years to come.

I enjoy working with Rustaceans across the globe, building a better and sustainable ecosystem for Rust community. If you like what we do, consider starring, commenting, sharing and contributing, it would be much appreciated.


Sanford Pun

I'm Sanford, an enthusiastic software engineer who enjoys problem-solving! I've worked on Rust for a couple of years now. During my early days with Rust, I focused more on the field of graphics/image processing, where I fell in love with what the language has to offer! This year, I've been exploring data engineering in the StarfishQL project.

A toast to the endless potential of Rust!

Communityโ€‹

If you are interested in the projects and want to share your thoughts, please star and watch the SeaQL/summer-of-code repository on GitHub and join us on our Discord server!

- + \ No newline at end of file diff --git a/blog/2022-07-17-whats-new-in-0.9.0/index.html b/blog/2022-07-17-whats-new-in-0.9.0/index.html index 396d238dbe1..b001a3de68c 100644 --- a/blog/2022-07-17-whats-new-in-0.9.0/index.html +++ b/blog/2022-07-17-whats-new-in-0.9.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.9.0

ยท 11 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.9.0 today! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#834] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

Proposed by:
Rob Gilson
boraarslan
Contributed by:
Billy Chan

Cursor Paginationโ€‹

[#822] Paginate models based on column(s) such as the primary key.

// Create a cursor that order by `cake`.`id`
let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id);

// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100
cursor.after(1).before(100);

// Get first 10 rows (order by `cake`.`id` ASC)
let rows: Vec<cake::Model> = cursor.first(10).all(db).await?;

// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order)
let rows: Vec<cake::Model> = cursor.last(10).all(db).await?;
Proposed by:
Lucas Berezy
Contributed by:
ร‰mile Fugulin
Billy Chan

Insert On Conflictโ€‹

[#791] Insert an active model with on conflict behaviour.

let orange = cake::ActiveModel {
id: ActiveValue::set(2),
name: ActiveValue::set("Orange".to_owned()),
};

// On conflict do nothing:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING
cake::Entity::insert(orange.clone())
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.do_nothing()
.to_owned()
)
.exec(db)
.await?;

// On conflict do update:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name"
cake::Entity::insert(orange)
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.update_column(cake::Column::Name)
.to_owned()
)
.exec(db)
.await?;
Proposed by:
baoyachi. Aka Rust Hairy crabs
Contributed by:
liberwang1013

Join Table with Custom Conditions and Table Aliasโ€‹

[#793, #852] Click Custom Join Conditions and Custom Joins to learn more.

assert_eq!(
cake::Entity::find()
.column_as(
Expr::tbl(Alias::new("fruit_alias"), fruit::Column::Name).into_simple_expr(),
"fruit_name"
)
.join_as(
JoinType::LeftJoin,
cake::Relation::Fruit
.def()
.on_condition(|_left, right| {
Expr::tbl(right, fruit::Column::Name)
.like("%tropical%")
.into_condition()
}),
Alias::new("fruit_alias")
)
.build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake`.`id`, `cake`.`name`, `fruit_alias`.`name` AS `fruit_name` FROM `cake`",
"LEFT JOIN `fruit` AS `fruit_alias` ON `cake`.`id` = `fruit_alias`.`cake_id` AND `fruit_alias`.`name` LIKE '%tropical%'",
]
.join(" ")
);
Proposed by:
Chris Tsang
Tuetuopay
Loรฏc
Contributed by:
Billy Chan
Matt
liberwang1013

(de)serialize Custom JSON Typeโ€‹

[#794] JSON stored in the database could be deserialized into custom struct in Rust.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
// JSON column defined in `serde_json::Value`
pub json: Json,
// JSON column defined in custom struct
pub json_value: KeyValue,
pub json_value_opt: Option<KeyValue>,
}

// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize`
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct KeyValue {
pub id: i32,
pub name: String,
pub price: f32,
pub notes: Option<String>,
}
Proposed by:
Mara Schulke
Chris Tsang
Contributed by:
Billy Chan

Derived Migration Nameโ€‹

[#736] Introduce DeriveMigrationName procedural macros to infer migration name from the file name.

use sea_orm_migration::prelude::*;

// Used to be...
pub struct Migration;

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

// Now... derive `DeriveMigrationName`,
// no longer have to specify the migration name explicitly
#[derive(DeriveMigrationName)]
pub struct Migration;

#[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
}
}
Proposed by:
Chris Tsang
Contributed by:
smonv
Lukas Potthast
Billy Chan

SeaORM CLI Improvementsโ€‹

  • [#735] Improve logging of generate entity command
  • [#588] Generate enum with numeric like variants
  • [#755] Allow old pending migration to be applied
  • [#837] Skip generating entity for ignored tables
  • [#724] Generate code for time crate
  • [#850] Add various blob column types
  • [#422] Generate entity files with Postgres's schema name
  • [#851] Skip checking connection string for credentials
Proposed & Contributed by:
ttys3
kyoto7250
yb3616
ร‰mile Fugulin
Bastian
Nahua
Mike
Frank Horvath
Maikel Wever

Miscellaneous Enhancementsโ€‹

  • [#800] Added sqlx_logging_level to ConnectOptions
  • [#768] Added num_items_and_pages to Paginator
  • [#849] Added TryFromU64 for time
  • [#853] Include column name in TryGetError::Null
  • [#778] Refactor stream metrics
Proposed & Contributed by:
SandaruKasa
Eric
ร‰mile Fugulin
Renato Dinhani
kyoto7250
Marco Napetti

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
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.10.x.

- + \ No newline at end of file diff --git a/blog/2022-07-30-engineering/index.html b/blog/2022-07-30-engineering/index.html index 4451c2c2a2b..193596dcdb5 100644 --- a/blog/2022-07-30-engineering/index.html +++ b/blog/2022-07-30-engineering/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Engineering at SeaQL.org

ยท 5 min read
Chris Tsang

It's hard to pin down the exact date, but I think SeaQL.org was setup in July 2020, a little over a year ago. Over the course of the year, SeaORM went from 0.1 to 0.9 and the number of users kept growing. I would like to outline our engineering process in this blog post, and perhaps it can serve as a reference or guidance to prospective contributors and the future maintainer of this project.

In the open source world, the Benevolent Dictator for Life (BDL) model underpins a number of successful open source projects. That's not me! As a maintainer, I believe in an open, bottom-up, iterative and progressive approach. Let me explain each of these words and what they mean to me.

Openโ€‹

Open as in source availability, but also engineering. We always welcome new contributors! We'd openly discuss ideas and designs. I would often explain why a decision was made in the first place for various things. The project is structured not as a monorepo, but several interdependent repos. This reduces the friction for new contributors, because they can have a smaller field of vision to focus on solving one particular problem at hand.

Bottom-upโ€‹

We rely on users to file feature requests, bug reports and of course pull requests to drive the project forward. The great thing is, for every feature / bug fix, there is a use case for it and a confirmation from a real user that it works and is reasonable. As a maintainer, I could not have first hand experience for all features and so could not understand some of the pain points.

Iterativeโ€‹

Open source software is imperfect, impermanent and incomplete. While I do have a grand vision in mind, we do not try rushing it all the way in one charge, nor keeping a project secret until it is 'complete'. Good old 'release early, release often' - we would release an initial working version of a tool, gather user feedback and improve upon it, often reimplementing a few things and break a few others - which brings us to the next point.

Progressiveโ€‹

Favour progression. Always look forward and leave legacy behind. It does not mean that we would arbitrary break things, but when a decision is made, we'd always imagine how the software should be without historic context. We'd provide migrate paths and encourage users to move forward with us. After all, Rust is a young and evolving language! You may or may not know that async was just stabilized in 2020.

Enough said for the philosophy, let's now talk about the actual engineering process.

1. Idea & Designโ€‹

We first have some vague idea on what problem we want to tackle. As we put in more details to the use case, we can define the problem and brainstorm solutions. Then we look for workable ways to implement that in Rust.

2. Implementationโ€‹

An initial proof of concept is appreciated. We iterate on the implementation to reduce the impact and improve the maintainability.

3. Testingโ€‹

We rely on automated tests. Every feature should come with corresponding tests, and a release is good if and only if all tests are green. Which means for features not covered by our test suite, it is an uncertainty to when we would break them. So if certain undocumented feature is important to you, we encourage you to add that to our test suite.

4. Documentationโ€‹

Coding is not complete without documentation. Rust doc tests kill two birds with one stone and so is greatly appreciated. For SeaORM we have separate documentation repository and tutorial repository. It takes a lot of effort to maintain those to be up to date, and right now it's mostly done by our core contributors.

5. Releaseโ€‹

We run on a release train model, although the frequency varies. The ethos is to have small number breaking changes often. At one point, SeaQuery has a new release every week. SeaORM runs on monthly, although it more or less relaxes to bimonthly now. At any time, we maintain two branches, the latest release and master. PRs are always merged into master, and if it is non-breaking (and worthy) I would backport it to the release branch and make a minor release. At the end, I want to maintain momentum and move forward together with the community. Users can have a rough expectation on when merges will be released. And there are just lots of change we cannot avoid a breaking release as of the current state of the Rust ecosystem. Users are advised to upgrade regularly, and we ship along many small improvements to encourage that.

Conclusionโ€‹

Open source software is a collaborative effort and thank you all who participated! Also a big thanks to SeaQL's core contributors who made wonders. If you have not already, I invite you to star all our repositories. If you want to support us materially, a small donation would make a big difference. SeaQL the organization is still in its infancy, and your support is vital to SeaQL's longevity and the prospect of the Rust community.

- + \ No newline at end of file diff --git a/blog/2022-08-05-whats-new-in-seaquery-0.26.0/index.html b/blog/2022-08-05-whats-new-in-seaquery-0.26.0/index.html index c9396a0bbe6..ebf47b0d63c 100644 --- a/blog/2022-08-05-whats-new-in-seaquery-0.26.0/index.html +++ b/blog/2022-08-05-whats-new-in-seaquery-0.26.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaQuery 0.26.0

ยท 3 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.26.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#356] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

VALUES listsโ€‹

[#351] Add support for VALUES lists

// SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x"
let query = SelectStatement::new()
.expr(Expr::asterisk())
.from_values(vec![(1i32, "hello"), (2, "world")], Alias::new("x"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x""#
);

Introduce sea-query-binderโ€‹

[#273] Native support SQLx without marcos

use sea_query_binder::SqlxBinder;

// Create SeaQuery query with prepare SQLx
let (sql, values) = Query::select()
.columns([
Character::Id,
Character::Uuid,
Character::Character,
Character::FontSize,
Character::Meta,
Character::Decimal,
Character::BigDecimal,
Character::Created,
Character::Inet,
Character::MacAddress,
])
.from(Character::Table)
.order_by(Character::Id, Order::Desc)
.build_sqlx(PostgresQueryBuilder);

// Execute query
let rows = sqlx::query_as_with::<_, CharacterStructChrono, _>(&sql, values)
.fetch_all(&mut pool)
.await?;

// Print rows
for row in rows.iter() {
println!("{:?}", row);
}

CASE WHEN statement supportโ€‹

[#304] Add support for CASE WHEN statement

let query = Query::select()
.expr_as(
CaseStatement::new()
.case(Expr::tbl(Glyph::Table, Glyph::Aspect).is_in(vec![2, 4]), Expr::val(true))
.finally(Expr::val(false)),
Alias::new("is_even")
)
.from(Glyph::Table)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT (CASE WHEN ("glyph"."aspect" IN (2, 4)) THEN TRUE ELSE FALSE END) AS "is_even" FROM "glyph""#
);

Add support for Ip(4,6)Network and MacAddressโ€‹

[#309] Add support for Network types in PostgreSQL backend

Introduce sea-query-attrโ€‹

[#296] Proc-macro for deriving Iden enum from struct

use sea_query::gen_type_def;

#[gen_type_def]
pub struct Hello {
pub name: String
}

println!("{:?}", HelloTypeDef::Name);

Add ability to alter foreign keysโ€‹

[#299] Add support for ALTER foreign Keys

let foreign_key_char = TableForeignKey::new()
.name("FK_character_glyph")
.from_tbl(Char::Table)
.from_col(Char::FontId)
.from_col(Char::Id)
.to_tbl(Glyph::Table)
.to_col(Char::FontId)
.to_col(Char::Id)
.to_owned();

let table = Table::alter()
.table(Character::Table)
.add_foreign_key(&foreign_key_char)
.to_owned();

assert_eq!(
table.to_string(PostgresQueryBuilder),
vec![
r#"ALTER TABLE "character""#,
r#"ADD CONSTRAINT "FK_character_glyph""#,
r#"FOREIGN KEY ("font_id", "id") REFERENCES "glyph" ("font_id", "id")"#,
r#"ON DELETE CASCADE ON UPDATE CASCADE,"#,
]
.join(" ")
);

Select DISTINCT ONโ€‹

[#250]

let query = Query::select()
.from(Char::Table)
.distinct_on(vec![Char::Character])
.column(Char::Character)
.column(Char::SizeW)
.column(Char::SizeH)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT DISTINCT ON ("character") "character", "size_w", "size_h" FROM "character""#
);

Miscellaneous Enhancementsโ€‹

  • [#353] Support LIKE ... ESCAPE ... expression
  • [#306] Move escape and unescape string to backend
  • [#365] Add method to make a column nullable
  • [#348] Add is & is_not to Expr
  • [#349] Add CURRENT_TIMESTAMP function
  • [#345] Add in_tuple method to Expr
  • [#266] Insert Default
  • [#324] Make sea-query-driver an optional dependency
  • [#334] Add ABS function
  • [#332] Support IF NOT EXISTS when create index
  • [#314] Support different blob types in MySQL
  • [#331] Add VarBinary column type
  • [#335] RETURNING expression supporting SimpleExpr

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

- + \ No newline at end of file diff --git a/blog/2022-08-12-3k-github-stars/index.html b/blog/2022-08-12-3k-github-stars/index.html index 96045f296b3..2369d510a9b 100644 --- a/blog/2022-08-12-3k-github-stars/index.html +++ b/blog/2022-08-12-3k-github-stars/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Celebrating 3,000+ GitHub Stars ๐ŸŽ‰

ยท 6 min read
SeaQL Team

We are celebrating the milestone of reaching 3,000 GitHub stars across all SeaQL repositories!

This wouldn't have happened without your support and contribution, so we want to thank the community for being with us along the way.

The Journeyโ€‹

SeaQL.org was founded back in 2020. We devoted ourselves into developing open source libraries that help Rust developers to build data intensive applications. In the past two years, we published and maintained four open source libraries: SeaQuery, SeaSchema, SeaORM and StarfishQL. Each library is designed to fill a niche in the Rust ecosystem, and they are made to play well with other Rust libraries.

2020โ€‹

  • Oct 2020: SeaQL founded
  • Dec 2020: SeaQuery first released

2021โ€‹

  • Apr 2021: SeaSchema first released
  • Aug 2021: SeaORM first released
  • Nov 2021: SeaORM reached 0.4.0
  • Dec 2021: SeaQuery reached 0.20.0
  • Dec 2021: SeaSchema reached 0.4.0

2022โ€‹

  • Apr 2022: SeaQL selected as a Google Summer of Code 2022 mentor organization
  • Apr 2022: StarfishQL first released
  • Jul 2022: SeaQuery reached 0.26.2
  • Jul 2022: SeaSchema reached 0.9.3
  • Jul 2022: SeaORM reached 0.9.1
  • Aug 2022: SeaQL reached 3,000+ GitHub stars

Where're We Now?โ€‹

We're pleased by the adoption by the Rust community. We couldn't make it this far without your feedback and contributions.

4 ๐Ÿ“ฆ
Open source projects
5 ๐Ÿฌ
Startups using SeaQL
1,972 ๐ŸŽˆ
Dependent projects
131 ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ
Contributors
1,061 โœ…
Merged PRs & resolved issues
3,158 โญ
GitHub stars
432 ๐Ÿ—ฃ๏ธ
Discord members
87,937 โŒจ๏ธ
Lines of Rust
667,769 ๐Ÿ’ฟ
Downloads on crates.io

* as of Aug 12

Core Membersโ€‹

Our team has grown from two people initially into four. We always welcome passionate engineers to join us!

Chris Tsang
Founder. Led the initial development and maintaining the projects.
Billy Chan
Founding member. Contributed many features and bug fixes. Keeps the community alive.
Ivan Krivosheev
Joined in 2022. Contributed many features and bug fixes, most notably to SeaQuery.
Sanford Pun
Developed StarfishQL and wrote SeaORM's tutorial.

Special Thanksโ€‹

Marco Napetti
Contributed transaction, streaming and tracing API to SeaORM.
nitnelave
Contributed binder crate and other improvements to SeaQuery.
Sam Samai
Developed SeaORM's test suite and demo schema.
Daniel Lyne
Developed SeaSchema's Postgres implementation.
Charles Chege
Developed SeaSchema's SQLite implementation.

Sponsorsโ€‹

If you are feeling generous, a small donation will be greatly appreciated.

A big shout out to our sponsors ๐Ÿ˜‡:

ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
Unnamed Sponsor

Contributorsโ€‹

Many features and enhancements are actually proposed and implemented by the community. We want to take this chance to thank all our contributors!

What's Next?โ€‹

We have two ongoing Summer of Code 2022 projects to enrich the SeaQL ecosystem, planning to be released later this year. In the meantime, we're focusing on improving existing SeaQL libraries until reaching version 1.0, we'd love to hear comments and feedback from the community.

If you like what we do, consider starring, commenting, sharing, contributing and together building for Rust's future!

- + \ No newline at end of file diff --git a/blog/2022-09-17-introducing-seaography/index.html b/blog/2022-09-17-introducing-seaography/index.html index a0a3146754f..2e18a8c9b79 100644 --- a/blog/2022-09-17-introducing-seaography/index.html +++ b/blog/2022-09-17-introducing-seaography/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Introducing Seaography ๐Ÿงญ

ยท 4 min read
SeaQL Team

What a fruitful Summer of Code! Today, we are excited to introduce Seaography to the SeaQL community. Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

Motivationโ€‹

We observed that other ecosystems have similar tools such as PostGraphile and Hasura allowing users to query a database via GraphQL with minimal effort upfront. We decided to bring that seamless experience to the Rust ecosystem.

For existing SeaORM users, adding a GraphQL API is straight forward. Start by adding seaography and async-graphql dependencies to your crate. Then, deriving a few extra derive macros to the SeaORM entities. Finally, spin up a GraphQL server to serve queries!

If you are new to SeaORM, no worries, we have your back. You only need to provide a database connection, and seaography-cli will generate the SeaORM entities together with a complete Rust project!

Designโ€‹

We considered two approaches in our initial discussion: 1) blackbox query engine 2) code generator. The drawback with a blackbox query engine is it's difficult to customize or extend its behaviour, making it difficult to develop and operate in the long run. We opted the code generator approach, giving users full control and endless possibilities with the versatile async Rust ecosystem.

This project is separated into the following crates:

  • seaography: The facade crate; exporting macros, structures and helper functions to turn SeaORM entities into GraphQL nodes.

  • seaography-cli: The CLI tool; it generates SeaORM entities along with a full Rust project based on a user-provided database.

  • seaography-discoverer: A helper crate used by the CLI tool to discover the database schema and transform into a generic format.

  • seaography-generator: A helper crate used by the CLI tool to consume the database schema and generate a full Rust project.

  • seaography-derive: A set of procedural macros to derive types and trait implementations on SeaORM entities, turning them into GraphQL nodes.

Featuresโ€‹

  • Relational query (1-to-1, 1-to-N)
  • Pagination on query's root entity
  • Filter with operators (e.g. gt, lt, eq)
  • Order by any column

Getting Startedโ€‹

To quick start, we have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

For more documentation, visit www.sea-ql.org/Seaography.

What's Next?โ€‹

This project passed the first milestone shipping the essential features, but it still has a long way to go. The next milestone would be:

  • Query enhancements
    • Filter related queries
    • Filter based on related queries properties
    • Paginate related queries
    • Order by related queries
  • Cursor based pagination
  • Single entity query
  • Mutations
    • Insert single entity
    • Insert batch entities
    • Update single entity
    • Update batch entities using filter
    • Delete single entity
    • Delete batch entities

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL
- + \ No newline at end of file diff --git a/blog/2022-09-27-getting-started-with-seaography/index.html b/blog/2022-09-27-getting-started-with-seaography/index.html index 92414fde9b0..572d3f6ea6f 100644 --- a/blog/2022-09-27-getting-started-with-seaography/index.html +++ b/blog/2022-09-27-getting-started-with-seaography/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Getting Started with Seaography

ยท 6 min read
SeaQL Team

Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

The design and implementation of Seaography can be found on our release blog post and documentation.

Extending a SeaORM projectโ€‹

Since Seaography is built on top of SeaORM, you can easily build a GraphQL server from a SeaORM project.

Start by adding Seaography and GraphQL dependencies to your Cargo.toml.

Cargo.toml
[dependencies]
sea-orm = { version = "^0.9", features = [ ... ] }
+ seaography = { version = "^0.1", features = [ "with-decimal", "with-chrono" ] }
+ async-graphql = { version = "4.0.10", features = ["decimal", "chrono", "dataloader"] }
+ async-graphql-poem = { version = "4.0.10" }

Then, derive a few macros on the SeaORM entities.

src/entities/film_actor.rs
use sea_orm::entity::prelude::*;

#[derive(
Clone,
Debug,
PartialEq,
DeriveEntityModel,
+ async_graphql::SimpleObject,
+ seaography::macros::Filter,
)]
+ #[graphql(complex)]
+ #[graphql(name = "FilmActor")]
#[sea_orm(table_name = "film_actor")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub actor_id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub film_id: i32,
pub last_update: DateTimeUtc,
}

#[derive(
Copy,
Clone,
Debug,
EnumIter,
DeriveRelation,
+ seaography::macros::RelationsCompact,
)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::film::Entity",
from = "Column::FilmId",
to = "super::film::Column::FilmId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Film,
#[sea_orm(
belongs_to = "super::actor::Entity",
from = "Column::ActorId",
to = "super::actor::Column::ActorId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Actor,
}

We also need to define QueryRoot for the GraphQL server. This define the GraphQL schema.

src/query_root.rs
#[derive(Debug, seaography::macros::QueryRoot)]
#[seaography(entity = "crate::entities::actor")]
#[seaography(entity = "crate::entities::film")]
#[seaography(entity = "crate::entities::film_actor")]
pub struct QueryRoot;
src/lib.rs
use sea_orm::prelude::*;

pub mod entities;
pub mod query_root;

pub use query_root::QueryRoot;

pub struct OrmDataloader {
pub db: DatabaseConnection,
}

Finally, create an executable to drive the GraphQL server.

src/main.rs
use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

Generating a project from databaseโ€‹

If all you have is a database schema, good news! You can setup a GraphQL server without writing a single line of code.

Install seaography-cli, it helps you generate SeaORM entities along with a full Rust project based on a database schema.

cargo install seaography-cli

Run seaography-cli to generate code for the GraphQL server.

# The command take three arguments
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Checkout the example projectsโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

Starting the GraphQL Serverโ€‹

Your GraphQL server is ready to launch! Go to the Rust project root then execute cargo run to spin it up.

$ cargo run

Playground: http://localhost:8000

Visit the GraphQL playground at http://localhost:8000

GraphQL Playground

Query Data via GraphQLโ€‹

Let say we want to get the first 3 films released on or after year 2006 sorted in ascending order of its title.

{
film(
pagination: { limit: 3, page: 0 }
filters: { releaseYear: { gte: "2006" } }
orderBy: { title: ASC }
) {
data {
filmId
title
description
releaseYear
filmActor {
actor {
actorId
firstName
lastName
}
}
}
pages
current
}
}

We got the following JSON result after running the GraphQL query.

{
"data": {
"film": {
"data": [
{
"filmId": 1,
"title": "ACADEMY DINOSAUR",
"description": "An Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": "2006",
"filmActor": [
{
"actor": {
"actorId": 1,
"firstName": "PENELOPE",
"lastName": "GUINESS"
}
},
{
"actor": {
"actorId": 10,
"firstName": "CHRISTIAN",
"lastName": "GABLE"
}
},
// ...
]
},
{
"filmId": 2,
"title": "ACE GOLDFINGER",
"description": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
"releaseYear": "2006",
"filmActor": [
// ...
]
},
// ...
],
"pages": 334,
"current": 0
}
}
}

Behind the scene, the following SQL were queried:

SELECT "film"."film_id",
"film"."title",
"film"."description",
"film"."release_year",
"film"."language_id",
"film"."original_language_id",
"film"."rental_duration",
"film"."rental_rate",
"film"."length",
"film"."replacement_cost",
"film"."rating",
"film"."special_features",
"film"."last_update"
FROM "film"
WHERE "film"."release_year" >= '2006'
ORDER BY "film"."title" ASC
LIMIT 3 OFFSET 0

SELECT "film_actor"."actor_id", "film_actor"."film_id", "film_actor"."last_update"
FROM "film_actor"
WHERE "film_actor"."film_id" IN (1, 3, 2)

SELECT "actor"."actor_id", "actor"."first_name", "actor"."last_name", "actor"."last_update"
FROM "actor"
WHERE "actor"."actor_id" IN (24, 162, 20, 160, 1, 188, 123, 30, 53, 40, 2, 64, 85, 198, 10, 19, 108, 90)

Under the hood, Seaography uses async_graphql::dataloader in querying nested objects to tackle the N+1 problem.

To learn more, checkout the Seaography Documentation.

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL
- + \ No newline at end of file diff --git a/blog/2022-10-31-whats-new-in-seaquery-0.27.0/index.html b/blog/2022-10-31-whats-new-in-seaquery-0.27.0/index.html index 1b2a9f7b8d6..57573bd18a4 100644 --- a/blog/2022-10-31-whats-new-in-seaquery-0.27.0/index.html +++ b/blog/2022-10-31-whats-new-in-seaquery-0.27.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaQuery 0.27.0

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.27.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#356] We have upgraded a major dependency:

  • Upgrade sqlx to 0.6.1

You might need to upgrade the corresponding dependency in your application as well.

Drivers supportโ€‹

We have reworked the way drivers work in SeaQuery: priori to 0.27.0, users have to invoke the sea_query_driver_* macros. Now each driver sqlx, postgres & rusqlite has their own supporting crate, which integrates tightly with the corresponding libraries. Checkout our integration examples below for more details.

[#383] Deprecate sea-query-driver in favour of sea-query-binder

[#422] Rusqlite support is moved to sea-query-rusqlite

[#433] Postgres support is moved to sea-query-postgres

// before
sea_query::sea_query_driver_postgres!();
use sea_query_driver_postgres::{bind_query, bind_query_as};

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build(PostgresQueryBuilder);

let row = bind_query(sqlx::query(&sql), &values)
.fetch_one(&mut pool)
.await
.unwrap();

// now
use sea_query_binder::SqlxBinder;

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build_sqlx(PostgresQueryBuilder);

let row = sqlx::query_with(&sql, values)
.fetch_one(&mut pool)
.await
.unwrap();

// You can now make use of SQLx's `query_as_with` nicely:
let rows = sqlx::query_as_with::<_, StructWithFromRow, _>(&sql, values)
.fetch_all(&mut pool)
.await
.unwrap();

Support sub-query operators: EXISTS, ALL, ANY, SOMEโ€‹

[#118] Added sub-query operators: EXISTS, ALL, ANY, SOME

let query = Query::select()
.column(Char::Id)
.from(Char::Table)
.and_where(
Expr::col(Char::Id)
.eq(
Expr::any(
Query::select().column(Char::Id).from(Char::Table).take()
)
)
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `id` FROM `character` WHERE `id` = ANY(SELECT `id` FROM `character`)"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "id" FROM "character" WHERE "id" = ANY(SELECT "id" FROM "character")"#
);

Support ON CONFLICT WHEREโ€‹

[#366] Added support to ON CONFLICT WHERE

let query = Query::insert()
.into_table(Glyph::Table)
.columns([Glyph::Aspect, Glyph::Image])
.values_panic(vec![
2.into(),
3.into(),
])
.on_conflict(
OnConflict::column(Glyph::Id)
.update_expr((Glyph::Image, Expr::val(1).add(2)))
.target_and_where(Expr::tbl(Glyph::Table, Glyph::Aspect).is_null())
.to_owned()
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `image` = 1 + 2"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);
assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);

Changed cond_where chaining semanticsโ€‹

[#414] Changed cond_where chaining semantics

// Before: will extend current Condition
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.cond_where(Expr::col(Glyph::Id).eq(3))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 OR "id" = 2 OR "id" = 3"#
);
// Before: confusing, since it depends on the order of invocation:
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(3))
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 3 AND ("id" = 1 OR "id" = 2)"#
);
// Now: will always conjoin with `AND`
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(1))
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 AND ("id" = 2 OR "id" = 3)"#
);
// Now: so they are now equivalent
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.cond_where(Expr::col(Glyph::Id).eq(1))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE ("id" = 2 OR "id" = 3) AND "id" = 1"#
);

Added OnConflict::value and OnConflict::valuesโ€‹

[#451] Implementation From<T> for any Into<Value> into SimpleExpr

// Before: notice the tuple
OnConflict::column(Glyph::Id).update_expr((Glyph::Image, Expr::val(1).add(2)))
// After: it accepts `Value` as well as `SimpleExpr`
OnConflict::column(Glyph::Id).value(Glyph::Image, Expr::val(1).add(2))

Improvement to ColumnDef::defaultโ€‹

[#347] ColumnDef::default now accepts Into<SimpleExpr> instead Into<Value>

// Now we can write:
ColumnDef::new(Char::FontId)
.timestamp()
.default(Expr::current_timestamp())

Breaking Changesโ€‹

  • [#386] Changed in_tuples interface to accept IntoValueTuple
  • [#320] Removed deprecated methods
  • [#440] CURRENT_TIMESTAMP changed from being a function to keyword
  • [#375] Update SQLite boolean type from integer to boolean`
  • [#451] Deprecated OnConflict::update_value, OnConflict::update_values, OnConflict::update_expr, OnConflict::update_exprs
  • [#451] Deprecated InsertStatement::exprs, InsertStatement::exprs_panic
  • [#451] Deprecated UpdateStatement::col_expr, UpdateStatement::value_expr, UpdateStatement::exprs
  • [#451] UpdateStatement::value now accept Into<SimpleExpr> instead of Into<Value>
  • [#451] Expr::case, CaseStatement::case and CaseStatement::finally now accepts Into<SimpleExpr> instead of Into<Expr>
  • [#460] InsertStatement::values, UpdateStatement::values now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Value>
  • [#409] Use native api from SQLx for SQLite to work with time
  • [#435] Changed type of ColumnType::Enum from (String, Vec<String>) to Enum { name: DynIden, variants: Vec<DynIden>}

Miscellaneous Enhancementsโ€‹

  • [#336] Added support one dimension Postgres array for SQLx
  • [#373] Support CROSS JOIN
  • [#457] Added support DROP COLUMN for SQLite
  • [#466] Added YEAR, BIT and VARBIT types
  • [#338] Handle Postgres schema name for schema statements
  • [#418] Added %, << and >> binary operators
  • [#329] Added RAND function
  • [#425] Implements Display for Value
  • [#427] Added INTERSECT and EXCEPT to UnionType
  • [#448] OrderedStatement::order_by_customs, OrderedStatement::order_by_columns, OverStatement::partition_by_customs, OverStatement::partition_by_columns now accepts IntoIterator<Item = T> instead of Vec<T>
  • [#452] TableAlterStatement::rename_column, TableAlterStatement::drop_column, ColumnDef::new, ColumnDef::new_with_type now accepts IntoIden instead of Iden
  • [#426] Cleanup IndexBuilder trait methods
  • [#436] Introduce SqlWriter trait
  • [#448] Remove unneeded vec! from examples

Bug Fixesโ€‹

  • [#449] distinct_on properly handles ColumnRef
  • [#461] Removed ON for DROP INDEX for SQLite
  • [#468] Change datetime string format to include microseconds
  • [#452] ALTER TABLE for PosgreSQL with UNIQUE constraint

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

- + \ No newline at end of file diff --git a/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io/index.html b/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io/index.html index d65c03d42dd..21f0a49cfdb 100644 --- a/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io/index.html +++ b/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Toggle Stacked Download Graph in crates.io

ยท 2 min read
SeaQL Team

Not long ago we opened a PR "Toggle stacked download graph #5010" resolving Convert download chart from stacked chart to regular chart #3876 for crates.io.

What's it all about?

Problemโ€‹

The download graph on crates.io used to be a stacked graph. With download count of older versions stack on top of newer versions. You might misinterpret the numbers. Consider this, at the first glance, it seems that version 0.9.2 has 1,500+ downloads on Nov 7. But in fact, it has only 237 downloads that day because the graph is showing the cumulative downloads.

crates.io Stacked Download Graph

This makes it hard to compare the download trend of different versions over time. Why this is important? You may ask. It's important to observe the adoption rate of newer version upon release. This paints a general picture if existing users are upgrading to newer version or not.

Solutionโ€‹

The idea is simple but effective: having a dropdown to toggle between stacked and unstacked download graph. With this, one can switch between both display mode, comparing the download trend of different version and observing the most download version in the past 90 days are straightforward and intuitive.

crates.io Unstacked Download Graph

Conclusionโ€‹

This is a great tool for us to gauge the adoption rate of our new releases and we highly encourage user upgrading to newer release that contains feature updates and bug fixes.

- + \ No newline at end of file diff --git a/blog/2022-11-10-whats-new-in-0.10.x/index.html b/blog/2022-11-10-whats-new-in-0.10.x/index.html index afda244afb2..1d3951c151e 100644 --- a/blog/2022-11-10-whats-new-in-0.10.x/index.html +++ b/blog/2022-11-10-whats-new-in-0.10.x/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaORM 0.10.x

ยท 7 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.10.0!

Rust 1.65โ€‹

The long-anticipated Rust 1.65 has been released! Generic associated types (GATs) must be the hottest newly-stabilized feature.

How is GAT useful to SeaORM? Let's take a look at the following:

trait StreamTrait<'a>: Send + Sync {
type Stream: Stream<Item = Result<QueryResult, DbErr>> + Send;

fn stream(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a + Send>>;
}

You can see that the Future has a lifetime 'a, but as a side effect the lifetime is tied to StreamTrait.

With GAT, the lifetime can be elided:

trait StreamTrait: Send + Sync {
type Stream<'a>: Stream<Item = Result<QueryResult, DbErr>> + Send
where
Self: 'a;

fn stream<'a>(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream<'a>, DbErr>> + 'a + Send>>;
}

What benefit does it bring in practice? Consider you have a function that accepts a generic ConnectionTrait and calls stream():

async fn processor<'a, C>(conn: &'a C) -> Result<...>
where C: ConnectionTrait + StreamTrait<'a> {...}

The fact that the lifetime of the connection is tied to the stream can create confusion to the compiler, most likely when you are making transactions:

async fn do_transaction<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + TransactionTrait
{
let txn = conn.begin().await?;
processor(&txn).await?;
txn.commit().await?;
}

But now, with the lifetime of the stream elided, it's much easier to work on streams inside transactions because the two lifetimes are now distinct and the stream's lifetime is implicit:

async fn processor<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + StreamTrait {...}

Big thanks to @nappa85 for the contribution.


Below are some feature highlights ๐ŸŒŸ:

Support Array Data Types in Postgresโ€‹

[#1132] Support model field of type Vec<T>. (by @hf29h8sh321, @ikrivosheev, @tyt2y3, @billy1624)

You can define a vector of types that are already supported by SeaORM in the model.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "collection")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub integers: Vec<i32>,
pub integers_opt: Option<Vec<i32>>,
pub floats: Vec<f32>,
pub doubles: Vec<f64>,
pub strings: Vec<String>,
}

Keep in mind that you need to enable the postgres-array feature and this is a Postgres only feature.

sea-orm = { version = "0.10", features = ["postgres-array", ...] }

Better Error Typesโ€‹

[#750, #1002] Error types with parsable database specific error. (by @mohs8421, @tyt2y3)

let mud_cake = cake::ActiveModel {
id: Set(1),
name: Set("Moldy Cake".to_owned()),
price: Set(dec!(10.25)),
gluten_free: Set(false),
serial: Set(Uuid::new_v4()),
bakery_id: Set(None),
};

// Insert a new cake with its primary key (`id` column) set to 1.
let cake = mud_cake.save(db).await.expect("could not insert cake");

// Insert the same row again and it failed
// because primary key of each row should be unique.
let error: DbErr = cake
.into_active_model()
.insert(db)
.await
.expect_err("inserting should fail due to duplicate primary key");

match error {
DbErr::Exec(RuntimeErr::SqlxError(error)) => match error {
Error::Database(e) => {
// We check the error code thrown by the database (MySQL in this case),
// `23000` means `ER_DUP_KEY`: we have a duplicate key in the table.
assert_eq!(e.code().unwrap(), "23000");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}

Run Migration on Any Postgres Schemaโ€‹

[#1056] By default migration will be run on the public schema, you can now override it when running migration on the CLI or programmatically. (by @MattGson, @nahuakang, @billy1624)

For CLI, you can specify the target schema with -s / --database_schema option:

  • via sea-orm-cli: sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema
  • via SeaORM migrator: cargo run -- -u postgres://root:root@localhost/database -s my_schema

You can also run the migration on the target schema programmatically:

let connect_options = ConnectOptions::new("postgres://root:root@localhost/database".into())
.set_schema_search_path("my_schema".into()) // Override the default schema
.to_owned();

let db = Database::connect(connect_options).await?

migration::Migrator::up(&db, None).await?;

Breaking Changesโ€‹

enum ColumnType {
// then
Enum(String, Vec<String>)

// now
Enum {
/// Name of enum
name: DynIden,
/// Variants of enum
variants: Vec<DynIden>,
}
...
}
  • A new method array_type was added to ValueType:
impl sea_orm::sea_query::ValueType for MyType {
fn array_type() -> sea_orm::sea_query::ArrayType {
sea_orm::sea_query::ArrayType::TypeName
}
...
}
  • ActiveEnum::name() changed return type to DynIden:
#[derive(Debug, Iden)]
#[iden = "category"]
pub struct CategoryEnum;

impl ActiveEnum for Category {
// then
fn name() -> String {
"category".to_owned()
}

// now
fn name() -> DynIden {
SeaRc::new(CategoryEnum)
}
...
}

SeaORM Enhancementsโ€‹

CLI Enhancementsโ€‹

Please check here for the complete changelog.

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Henrik Giesel
Jacob Trueb
Marcus Buffett
Unnamed Sponsor
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.11.x.

- + \ No newline at end of file diff --git a/blog/2022-12-02-whats-new-in-seaography-0.3.0/index.html b/blog/2022-12-02-whats-new-in-seaography-0.3.0/index.html index 18cc2329d51..ffbb053dbd0 100644 --- a/blog/2022-12-02-whats-new-in-seaography-0.3.0/index.html +++ b/blog/2022-12-02-whats-new-in-seaography-0.3.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in Seaography 0.3.0

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release Seaography 0.3.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#93] We have upgraded a major dependency:

You might need to upgrade the corresponding dependency in your application as well.

Support Self Referencing Relationโ€‹

[#99] You can now query self referencing models and the inverse of it.

Self referencing relation should be added to the Relation enum, note that the belongs_to attribute must be belongs_to = "Entity".

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

#[derive(
Clone, Debug, PartialEq, DeriveEntityModel,
async_graphql::SimpleObject, seaography::macros::Filter,
)]
#[sea_orm(table_name = "staff")]
#[graphql(complex)]
#[graphql(name = "Staff")]
pub struct Model {
#[sea_orm(primary_key)]
pub staff_id: i32,
pub first_name: String,
pub last_name: String,
pub reports_to_id: Option<i32>,
}

#[derive(
Copy, Clone, Debug, EnumIter, DeriveRelation,
seaography::macros::RelationsCompact
)]
pub enum Relation {
#[sea_orm(
belongs_to = "Entity",
from = "Column::ReportsToId",
to = "Column::StaffId",
)]
SelfRef,
}

impl ActiveModelBehavior for ActiveModel {}

Then, you can query the related models in GraphQL.

{
staff {
nodes {
firstName
reportsToId
selfRefReverse {
staffId
firstName
}
selfRef {
staffId
firstName
}
}
}
}

The resulting JSON

{
"staff": {
"nodes": [
{
"firstName": "Mike",
"reportsToId": null,
"selfRefReverse": [
{
"staffId": 2,
"firstName": "Jon"
}
],
"selfRef": null
},
{
"firstName": "Jon",
"reportsToId": 1,
"selfRefReverse": null,
"selfRef": {
"staffId": 1,
"firstName": "Mike"
}
}
]
}
}

Web Framework Generatorโ€‹

[#74] You can generate seaography project with either Actix or Poem as the web server.

CLI Generator Optionโ€‹

Run seaography-cli to generate seaography code with Actix or Poem as the web framework.

# The command take three arguments, generating project with Poem web framework by default
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# Generating project with Actix web framework
seaography-cli -f actix <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Actixโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
use sea_orm::Database;
use seaography_example_project::*;
// ...

async fn graphql_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(
playground_source(GraphQLPlaygroundConfig::new("http://localhost:8000"))
))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader {
db: database.clone(),
},
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = App::new()
.app_data(Data::new(schema.clone()))
.service(web::resource("/").guard(guard::Post()).to(index))
.service(web::resource("/").guard(guard::Get()).to(graphql_playground));

HttpServer::new(app)
.bind("127.0.0.1:8000")?
.run()
.await
}

Poemโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

[#84] Filtering, sorting and paginating related 1-to-many queries. Note that the pagination is work-in-progress, currently it is in memory pagination.

For example, find all inactive customers, include their address, and their payments with amount greater than 7 ordered by amount the second result. You can execute the query below at our GraphQL playground.

{
customer(
filters: { active: { eq: 0 } }
pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } }
) {
nodes {
customerId
lastName
email
address {
address
}
payment(
filters: { amount: { gt: "7" } }
orderBy: { amount: ASC }
pagination: { pages: { limit: 1, page: 1 } }
) {
nodes {
paymentId
amount
}
pages
current
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
pageInfo {
hasPreviousPage
hasNextPage
endCursor
}
}
}

Integration Examplesโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

Communityโ€‹

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

- + \ No newline at end of file diff --git a/blog/2022-12-30-whats-new-in-seaquery-0.28.0/index.html b/blog/2022-12-30-whats-new-in-seaquery-0.28.0/index.html index 956d2a160f2..05f6b9e55d1 100644 --- a/blog/2022-12-30-whats-new-in-seaquery-0.28.0/index.html +++ b/blog/2022-12-30-whats-new-in-seaquery-0.28.0/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaQuery 0.28.0

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.28.0! Here are some feature highlights ๐ŸŒŸ:

New IdenStatic trait for static identifierโ€‹

[#508] Representing a identifier with &'static str. The IdenStatic trait looks like this:

pub trait IdenStatic: Iden + Copy + 'static {
fn as_str(&self) -> &'static str;
}

You can derive it easily for your existing Iden. Just changing the #[derive(Iden)] into #[derive(IdenStatic)].

#[derive(IdenStatic)]
enum User {
Table,
Id,
FirstName,
LastName,
#[iden = "_email"]
Email,
}

assert_eq!(User::Email.as_str(), "_email");

New PgExpr and SqliteExpr traits for backend specific expressionsโ€‹

[#519] Postgres specific and SQLite specific expressions are being moved into its corresponding trait. You need to import the trait into scope before construct the expression with those backend specific methods.

// Importing `PgExpr` trait before constructing Postgres expression
use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *};

let query = Query::select()
.columns([Font::Name, Font::Variant, Font::Language])
.from(Font::Table)
.and_where(Expr::val("a").concatenate("b").concat("c").concat("d"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "name", "variant", "language" FROM "font" WHERE 'a' || 'b' || 'c' || 'd'"#
);
// Importing `SqliteExpr` trait before constructing SQLite expression
use sea_query::{extension::sqlite::SqliteExpr, tests_cfg::*, *};

let query = Query::select()
.column(Font::Name)
.from(Font::Table)
.and_where(Expr::col(Font::Name).matches("a"))
.to_owned();

assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"SELECT "name" FROM "font" WHERE "name" MATCH 'a'"#
);

Bug Fixesโ€‹

// given
let (statement, values) = sea_query::Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(Cond::any()
.add(Cond::all()) // empty all() => TRUE
.add(Cond::any()) // empty any() => FALSE
)
.build(sea_query::MysqlQueryBuilder);

// old behavior
assert_eq!(statement, r#"SELECT `id` FROM `glyph`"#);

// new behavior
assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE (TRUE) OR (FALSE)"#
);

// a complex example
let (statement, values) = Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(
Cond::all()
.add(Cond::all().not())
.add(Cond::any().not())
.not(),
)
.build(MysqlQueryBuilder);

assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE NOT ((NOT TRUE) AND (NOT FALSE))"#
);

Breaking Changesโ€‹

  • [#535] MSRV is up to 1.62
# Make sure you're running SeaQuery with Rust 1.62+ ๐Ÿฆ€
$ rustup update
  • [#492] ColumnType::Array definition changed from Array(SeaRc<Box<ColumnType>>) to Array(SeaRc<ColumnType>)
  • [#475] Func::* now returns FunctionCall instead of SimpleExpr
  • [#475] Func::coalesce now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Into<SimpleExpr>
  • [#475] Removed Expr::arg and Expr::args - these functions are no longer needed
  • [#507] Moved all Postgres specific operators to PgBinOper
  • [#476] Expr methods used to accepts Into<Value> now accepts Into<SimpleExpr>
  • [#476] Expr::is_in, Expr::is_not_in now accepts Into<SimpleExpr> instead of Into<Value> and convert it to SimpleExpr::Tuple instead of SimpleExpr::Values
  • [#475] Expr::expr now accepts Into<SimpleExpr> instead of SimpleExpr
  • [#519] Moved Postgres specific Expr methods to new trait PgExpr
  • [#528] Expr::equals now accepts C: IntoColumnRef instead of T: IntoIden, C: IntoIden
use sea_query::{*, tests_cfg::*};

let query = Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.and_where(
Expr::col((Char::Table, Char::FontId))
- .equals(Font::Table, Font::Id)
+ .equals((Font::Table, Font::Id))
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `character`.`font_id` = `font`.`id`"#
);
  • [#525] Removed integer and date time column types' display length / precision option

API Additionsโ€‹

  • [#475] Added SelectStatement::from_function
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.column(ColumnRef::Asterisk)
.from_function(Func::random(), Alias::new("func"))
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT * FROM RAND() AS `func`"#
);
  • [#486] Added binary operators from the Postgres pg_trgm extension
use sea_query::extension::postgres::PgBinOper;

assert_eq!(
Query::select()
.expr(Expr::col(Font::Name).binary(PgBinOper::WordSimilarity, Expr::value("serif")))
.from(Font::Table)
.to_string(PostgresQueryBuilder),
r#"SELECT "name" <% 'serif' FROM "font""#
);
  • [#473] Added ILIKE and NOT ILIKE operators
  • [#510] Added the mul and div methods for SimpleExpr
  • [#513] Added the MATCH, -> and ->> operators for SQLite
use sea_query::extension::sqlite::SqliteBinOper;

assert_eq!(
Query::select()
.column(Char::Character)
.from(Char::Table)
.and_where(Expr::col(Char::Character).binary(SqliteBinOper::Match, Expr::val("test")))
.build(SqliteQueryBuilder),
(
r#"SELECT "character" FROM "character" WHERE "character" MATCH ?"#.to_owned(),
Values(vec!["test".into()])
)
);
  • [#497] Added the FULL OUTER JOIN
  • [#530] Added PgFunc::get_random_uuid
  • [#528] Added SimpleExpr::eq, SimpleExpr::ne, Expr::not_equals
  • [#529] Added PgFunc::starts_with
  • [#535] Added Expr::custom_keyword and SimpleExpr::not
use sea_query::*;

let query = Query::select()
.expr(Expr::custom_keyword(Alias::new("test")))
.to_owned();

assert_eq!(query.to_string(MysqlQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(PostgresQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(SqliteQueryBuilder), r#"SELECT test"#);
  • [#539] Added SimpleExpr::like, SimpleExpr::not_like and Expr::cast_as
  • [#532] Added support for NULLS NOT DISTINCT clause for Postgres
  • [#531] Added Expr::cust_with_expr and Expr::cust_with_exprs
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.expr(Expr::cust_with_expr("data @? ($1::JSONPATH)", "hello"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT data @? ('hello'::JSONPATH)"#
);
  • [#538] Added support for converting &String to Value

Miscellaneous Enhancementsโ€‹

  • [#475] New struct FunctionCall which hold function and arguments
  • [#503] Support BigDecimal, IpNetwork and MacAddress for sea-query-postgres
  • [#511] Made value::with_array module public and therefore making NotU8 trait public
  • [#524] Drop the Sized requirement on implementers of SchemaBuilders

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

- + \ No newline at end of file diff --git a/blog/2023-01-01-call-for-contributors-n-reviewers/index.html b/blog/2023-01-01-call-for-contributors-n-reviewers/index.html index 6963a954faf..2ffa64637d4 100644 --- a/blog/2023-01-01-call-for-contributors-n-reviewers/index.html +++ b/blog/2023-01-01-call-for-contributors-n-reviewers/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Call for Contributors and Reviewers ๐Ÿ“ข

ยท 4 min read
Chris Tsang

We are calling for contributors and reviewers for SeaQL projects ๐Ÿ“ข!

The SeaQL userbase has been steadily growing in the past year, and itโ€™s a pleasure for us to have helped individuals and start-ups to build their projects in Rust. However, the volume of questions, issues and pull requests is nearly saturating our core membersโ€™ capacity.

But again, thank you everyone for participating in the community!

If your project depends on SeaQL and you want to help us, here are some suggestions (if you have not already, star all our repositories and follow us on Twitter):

  1. Financial Contribution. You can sponsor us on GitHub and those will be used to cover our expenses. As a courtesy, we listen to our sponsors for their needs and use cases, and we also communicate our organizational development from time-to-time.
  2. Code Contribution. Opening a PR with us is always appreciated! To get started, you can go through our issue trackers and pick one to handle. If you are thinking of developing a substantial feature, start with drafting a "Proposal & Implementation Plan" (PIP).
  3. Knowledge Contribution. There are various formats of knowledge sharing: tutorial, cookbook, QnA and Discord. You can open PRs to our documentation repositories or publish on your own. We will be happy to list it in our learning resources section. Keep an eye on our GitHub Discussions and Discord and help others where you can!
  4. Code Review. This is an important process of our engineering. Right now, only 3 of our core members serve as reviewers. Non-core members can also become reviewers and I invite you to become one!

Now, Iโ€™d like to outline our review policy: for maturing projects, each PR merged has to be approved by at least two reviewers and one of them must be a core member; self-review allowed. Here are some examples:

  • A core member opened a PR, another core member approved โœ…
  • A core member opened a PR, a reviewer approved โœ…
  • A reviewer opened a PR, a core member approved โœ…
  • A reviewer opened a PR, another reviewer approved โ›”
  • A contributor opened a PR, 2 core members approved โœ…
  • A contributor opened a PR, a core member and a reviewer approved โœ…
  • A contributor opened a PR, 2 reviewers approved โ›”

In a nutshell, at least two pairs of trusted eyes should have gone through each PR.

What are the criteria when reviewing a PR?โ€‹

The following questions should all be answered yes.

  1. Implementation, documentation and tests
    1. Is the implementation easy to follow (have meaningful variable and function names)?
    2. Is there sufficient document to the API?
    3. Are there adequate tests covering various cases?
  2. API design
    1. Is the API self-documenting so users can understand its use easily?
    2. Is the API style consistent with our existing API?
    3. Does the API made reasonable use of the type system to enforce constraints?
    4. Are the failure paths and error messages clear?
    5. Are all breaking changes justified and documented?
  3. Functionality
    1. Does the feature make sense in computer science terms?
    2. Does the feature actually work with all our supported backends?
    3. Are all caveats discussed and eliminated / documented?
  4. Architecture
    1. Does it fit with the existing architecture of our codebase?
    2. Is it not going to create technical debt / maintenance burden?
    3. Does it not break abstraction?

1, 2 & 3 are fairly objective and factual, however the answers to 4 probably require some discussion and debate. If a consensus cannot be made, @tyt2y3 will make the final verdict.

Who are the current reviewers?โ€‹

As of today, SeaQL has 3 core members who are also reviewers:

Chris Tsang
Founder. Maintains all projects.
Billy Chan
Founding member. Co-maintainer of SeaORM and Seaography.
Ivan Krivosheev
Joined in 2022. Co-maintainer of SeaQuery.

How to become a reviewer?โ€‹

We are going to invite a few contributors we worked closely with, but you can also volunteer โ€“ the requirement is: you have made substantial code contribution to our projects, and has shown familiarity with our engineering practices.

Over time, when you have made significant contribution to our organization, you can also become a core member.

Letโ€™s build for Rust's future together ๐Ÿฆ€โ€‹

- + \ No newline at end of file diff --git a/blog/2023-01-28-internship-at-seaql/index.html b/blog/2023-01-28-internship-at-seaql/index.html index 26682af58e5..3499a132a17 100644 --- a/blog/2023-01-28-internship-at-seaql/index.html +++ b/blog/2023-01-28-internship-at-seaql/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Internship @ SeaQL

ยท 4 min read
SeaQL Team

SeaQL.org offer internships tailored to university students. In fact, it will be the 3rd cohort in 2023.

The internships normally take place during summer and winter semester breaks. During the internship period, you will work on a project dedicatedly and publish the projectโ€™s outcome at the end.

The striking aspect of our mode of operation is it covers the entire lifecycle of software development, from Design โžก๏ธ Implementation โžก๏ธ Testing โžก๏ธ Delivery. You will be amazed of how much you can achieve in such a short period of time!

To date StarfishQL โœด๏ธ and Seaography ๐Ÿงญ are great projects our team has created in the past year. We pride ourselves on careful planning, consistent execution, and pragmatic approach in software engineering. I spend a huge amount of time on idea evaluation: if the scope of the project is too small, it will be uninteresting, but if it is too large, it will fail to be delivered.

Fellow undergraduates, here are a few good reasons why you should participate in internships at an open-source organization like SeaQL:

  1. A tangible showcase on CV: open-source work is published, inspectable and has real-world impact. We will also ensure that it has good branding, graphics, and visibility.
  2. Not driven by a business process, we do not compromise on quality of work. We do not have a proprietary development process, so itโ€™s all open-source tools with transferable skills.
  3. You will contribute to the community and will interact with people across the world. Collaboration on open source is the best thing humanity ever invented. You will only believe me when you have experienced it first-hand.
  4. Because you are the driver of the project you work on, it allows you to uncover something more about yourself, in particular - abilities and discipline: you always have had under/over-estimated yourself in one aspect or another.

Here are several things you are going to learn:

  1. "Thinking > Programming": the more time you spend on thinking beforehand, the better the code you are going to write. And the more time you spend on reviewing afterwards, the better the code is going to be.
  2. How to organize a codebase. Make good use of the Rust type system to craft a modular, testable codebase.
  3. Test automation. Every SeaQL project is continuously tested, and this is an integral part of our engineering process.
  4. Documentation. Our software aims to provide good documentation that is comprehensive, easy to follow, and fun to read.
  5. Performance tuning. Depending on the project, we might do some benchmarking and optimization. But in general we put you off from writing code that creates unnecessary overhead.

We were a mentor organization in GSoC 2022 and may be in 2023 (update: we were not accepted into GSoC 2023). We also offer internships outside of GSoC. So, what are the requirements when you become a contributor?

  1. Be passionate. You must show your passion in open-source and software engineering, so a good GitHub profile with some participation is needed.
  2. Be dedicated. This is a full-time job. While being fully remote and flexible on hours, you must have no other commitment or duties during the stipulated internship period.
  3. Be open-minded. You should listen carefully to your mentors and act on their advice accordingly.
  4. Write more. Communicate your thoughts and progress on all channels in an organized manner.

Donโ€™t just listen to me though. Here is what our past interns says:

Be well-prepared for your upcoming career in this technology industry! Follow us on GitHub and Twitter now, and stay tuned for future announcements.

- + \ No newline at end of file diff --git a/blog/2023-02-05-faq-02/index.html b/blog/2023-02-05-faq-02/index.html index 161bb64b573..5fefbfd4a81 100644 --- a/blog/2023-02-05-faq-02/index.html +++ b/blog/2023-02-05-faq-02/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

SeaORM FAQ.02

ยท 2 min read
Chris Tsang

FAQ.02 Why the empty enum Relation {} is needed even if an Entity has no relations?โ€‹

Consider the following example Post Entity:

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

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "posts")]
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 {}

The two lines for defining Relation is quite unnecessary right?

To explain the problem, let's dive slightly deeper into the macro-expanded entity:

The DeriveRelation macro simply implements the RelationTrait:

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => unreachable!()
}
}
}

Which in turn is needed by EntityTrait as an associated type:

impl EntityTrait for Entity {
type Relation = Relation;
...
}

It would be ideal if, when the user does not specify this associated type, the library automatically fills in a stub to satisfy the type system?

Turns out, there is such a feature in Rust! It is an unstable feature called associated_type_defaults.

Basically, it allows trait definitions to specify a default associated type, allowing it to be elided:

// only compiles in nightly
trait EntityTrait {
type Relation: Relation = EmptyRelation;
}

Due to our commitment to stable Rust, this may not land in SeaORM very soon. When it is stabilized, do remind us to implement this feature to get rid of those two lines!

- + \ No newline at end of file diff --git a/blog/2023-02-08-whats-new-in-seaorm-0.11.0/index.html b/blog/2023-02-08-whats-new-in-seaorm-0.11.0/index.html index c701dbba71f..db2a2b8b8c7 100644 --- a/blog/2023-02-08-whats-new-in-seaorm-0.11.0/index.html +++ b/blog/2023-02-08-whats-new-in-seaorm-0.11.0/index.html @@ -10,14 +10,14 @@ - +
Skip to main content

What's new in SeaORM 0.11.0

ยท 10 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.11.0!

Data Loaderโ€‹

[#1443, #1238] The LoaderTrait provides an API to load related entities in batches.

Consider this one to many relation:

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

The generated SQL is:

SELECT
"cake"."id" AS "A_id",
"cake"."name" AS "A_name",
"fruit"."id" AS "B_id",
"fruit"."name" AS "B_name",
"fruit"."cake_id" AS "B_cake_id"
FROM "cake"
LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id"
ORDER BY "cake"."id" ASC

The 1 side's (Cake) data will be duplicated. If N is a large number, this would results in more data being transferred over the wire. Using the Loader would ensure each model is transferred only once.

The following loads the same data as above, but with two queries:

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

for (cake, fruits) in cakes.into_iter().zip(fruits.into_iter()) { .. }
SELECT "cake"."id", "cake"."name" FROM "cake"
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit" WHERE "fruit"."cake_id" IN (..)

You can even apply filters on the related entity:

let fruits_in_stock: Vec<Vec<fruit::Model>> = cakes.load_many(
fruit::Entity::find().filter(fruit::Column::Stock.gt(0i32))
db
).await?;
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit"
WHERE "fruit"."stock" > 0 AND "fruit"."cake_id" IN (..)

To learn more, read the relation docs.

Transaction Isolation Level and Access Modeโ€‹

[#1230] The transaction_with_config and begin_with_config allows you to specify the IsolationLevel and AccessMode.

For now, they are only implemented for MySQL and Postgres. In order to align their semantic difference, MySQL will execute SET TRANSACTION commands before begin transaction, while Postgres will execute SET TRANSACTION commands after begin transaction.

db.transaction_with_config::<_, _, DbErr>(
|txn| { ... },
Some(IsolationLevel::ReadCommitted),
Some(AccessMode::ReadOnly),
)
.await?;

let transaction = db
.begin_with_config(IsolationLevel::ReadCommitted, AccessMode::ReadOnly)
.await?;

To learn more, read the transaction docs.

Cast Column Type on Select and Saveโ€‹

[#1304] If you need to select a column as one type but save it into the database as another, you can specify the select_as and the save_as attributes to perform the casting. A typical use case is selecting a column of type citext (case-insensitive text) as String in Rust and saving it into the database as citext. One should define the model field as below:

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ci_table")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(select_as = "text", save_as = "citext")]
pub case_insensitive_text: String
}

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

impl ActiveModelBehavior for ActiveModel {}

Changes to ActiveModelBehaviorโ€‹

[#1328, #1145] The methods of ActiveModelBehavior now have Connection as an additional parameter. It enables you to perform database operations, for example, logging the changes made to the existing model or validating the data before inserting it.

#[async_trait]
impl ActiveModelBehavior for ActiveModel {
/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self {
Self {
uuid: Set(Uuid::new_v4()),
..ActiveModelTrait::default()
}
}

/// Will be triggered before insert / update
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// Logging changes
edit_log::ActiveModel {
action: Set("before_save".into()),
values: Set(serde_json::json!(model)),
..Default::default()
}
.insert(db)
.await?;

Ok(self)
}
}

To learn more, read the entity docs.

Execute Unprepared SQL Statementโ€‹

[#1327] You can execute an unprepared SQL statement with ConnectionTrait::execute_unprepared.

// Use `execute_unprepared` if the SQL statement doesn't have value bindings
db.execute_unprepared(
"CREATE TABLE `cake` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL
)"
)
.await?;

// Construct a `Statement` if the SQL contains value bindings
let stmt = Statement::from_sql_and_values(
manager.get_database_backend(),
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
["Cheese Cake".into()]
);
db.execute(stmt).await?;

Select Into Tupleโ€‹

[#1311] You can select a tuple (or single value) with the into_tuple method.

let res: Vec<(String, i64)> = cake::Entity::find()
.select_only()
.column(cake::Column::Name)
.column(cake::Column::Id.count())
.group_by(cake::Column::Name)
.into_tuple()
.all(&db)
.await?;

Atomic Migrationโ€‹

[#1379] Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite.

You can start a transaction inside each migration to perform operations like seeding sample data for a newly created table.

Types Supportโ€‹

  • [#1325] Support various UUID formats that are available in uuid::fmt module
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "uuid_fmt")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub uuid: Uuid,
pub uuid_braced: uuid::fmt::Braced,
pub uuid_hyphenated: uuid::fmt::Hyphenated,
pub uuid_simple: uuid::fmt::Simple,
pub uuid_urn: uuid::fmt::Urn,
}
  • [#1210] Support vector of enum for Postgres
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub teas: Vec<Tea>,
pub teas_opt: Option<Vec<Tea>>,
}
  • [#1414] Support ActiveEnum field as primary key
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_primary_key")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Tea,
pub category: Option<Category>,
pub color: Option<Color>,
}

Opt-in Unstable Internal APIsโ€‹

By enabling sea-orm-internal feature you opt-in unstable internal APIs including:

Breaking Changesโ€‹

  • [#1366] sea-query has been upgraded to 0.28.x, which comes with some improvements and breaking changes. Please follow the release notes for more details

  • [#1420] sea-orm-cli: generate entity command enable --universal-time flag by default

  • [#1425] Added RecordNotInserted and RecordNotUpdated to DbErr

  • [#1327] Added ConnectionTrait::execute_unprepared method

  • [#1311] The required method of TryGetable changed:

// then
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>;
// now; ColIdx can be `&str` or `usize`
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError>;

So if you implemented it yourself:

impl TryGetable for XXX {
- fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+ fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
- let value: YYY = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
+ let value: YYY = res.try_get_by(idx).map_err(TryGetError::DbErr)?;
..
}
}
  • [#1328] The ActiveModelBehavior trait becomes async trait. If you overridden the default ActiveModelBehavior implementation:
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// ...
}

// ...
}
  • [#1425] DbErr::RecordNotFound("None of the database rows are affected") is moved to a dedicated error variant DbErr::RecordNotUpdated
let res = Update::one(cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.into_active_model()
})
.exec(&db)
.await;

// then
assert_eq!(
res,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);

// now
assert_eq!(res, Err(DbErr::RecordNotUpdated));
  • [#1395] sea_orm::ColumnType was replaced by sea_query::ColumnType
    • Method ColumnType::def was moved to ColumnTypeTrait
    • ColumnType::Binary becomes a tuple variant which takes in additional option sea_query::BlobSize
    • ColumnType::Custom takes a sea_query::DynIden instead of String and thus a new method custom is added (note the lowercase)
// Compact Entity
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "fruit")]
pub struct Model {
- #[sea_orm(column_type = r#"Custom("citext".to_owned())"#)]
+ #[sea_orm(column_type = r#"custom("citext")"#)]
pub column: String,
}
// Expanded Entity
impl ColumnTrait for Column {
type EntityName = Entity;

fn def(&self) -> ColumnDef {
match self {
- Self::Column => ColumnType::Custom("citext".to_owned()).def(),
+ Self::Column => ColumnType::custom("citext").def(),
}
}
}

SeaORM Enhancementsโ€‹

  • [#1256] Refactor schema module to expose functions for database alteration
  • [#1346] Generate compact entity with #[sea_orm(column_type = "JsonBinary")] macro attribute
  • MockDatabase::append_exec_results(), MockDatabase::append_query_results(), MockDatabase::append_exec_errors() and MockDatabase::append_query_errors() [#1367] take any types implemented IntoIterator trait
  • [#1362] find_by_id and delete_by_id take any Into primary key value
  • [#1410] QuerySelect::offset and QuerySelect::limit takes in Into<Option<u64>> where None would reset them
  • [#1236] Added DatabaseConnection::close
  • [#1381] Added is_null getter for ColumnDef
  • [#1177] Added ActiveValue::reset to convert Unchanged into Set
  • [#1415] Added QueryTrait::apply_if to optionally apply a filter
  • Added the sea-orm-internal feature flag to expose some SQLx types
    • [#1297] Added DatabaseConnection::get_*_connection_pool() for accessing the inner SQLx connection pool
    • [#1434] Re-exporting SQLx errors

CLI Enhancementsโ€‹

  • [#846, #1186, #1318] Generate #[serde(skip_deserializing)] for primary key columns
  • [#1171, #1320] Generate #[serde(skip)] for hidden columns
  • [#1124, #1321] Generate entity with extra derives and attributes for model struct

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

Our GitHub Sponsor profile is up! SeaQL.org is an independent open-source organization run by passionate developers. If you enjoy using SeaORM, please star and share our repositories. If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the project.

A big shout out to our sponsors ๐Ÿ˜‡:

Afonso Barracha
ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Nick Price
Roland Gorรกcz
Henrik Giesel
Jacob Trueb
Naoki Ikeguchi
Manfred Lee
Marcus Buffett
efrain2007

What's Next?โ€‹

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

Here is the roadmap for SeaORM 0.12.x.

- + \ No newline at end of file diff --git a/blog/2023-04-03-intro-sea-streamer/index.html b/blog/2023-04-03-intro-sea-streamer/index.html index 565bfdd468a..1a233aa1d37 100644 --- a/blog/2023-04-03-intro-sea-streamer/index.html +++ b/blog/2023-04-03-intro-sea-streamer/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Introducing SeaStreamer ๐ŸŒŠ

ยท 5 min read
Chris Tsang

We are pleased to introduce SeaStreamer to the Rust community today. SeaStreamer is a stream processing toolkit to help you build stream processors in Rust.

At SeaQL we want to make Rust the best programming platform for data engineering. Where SeaORM is the essential tool for working with SQL databases, SeaStreamer aims to be your essential toolkit for working with streams.

Currently SeaStreamer provides integration with Kafka and Redis.

Let's have a quick tour of SeaStreamer.

High level async APIโ€‹

  • High level async API that supports both async-std and tokio
  • Mutex-free implementation1: concurrency achieved by message passing
  • A comprehensive type system that guides/restricts you with the API

Below is a basic Kafka consumer:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "kafka://streamer.sea-ql.org:9092/my_stream".parse()?;
let streamer = KafkaStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = KafkaConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_offset_reset(AutoOffsetReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Consumer::stream() returns an object that implements the Stream trait, which allows you to do neat things:

let items = consumer
.stream()
.take(num)
.map(process_message)
.collect::<Vec<_>>()
.await

Trait-based abstract interfaceโ€‹

All SeaStreamer backends implement a common abstract interface, offering you a familiar API. Below is a basic Redis consumer, which is nearly the same as the previous example:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "redis://localhost:6379/my_stream".parse()?;
let streamer = RedisStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = RedisConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_stream_reset(AutoStreamReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Redis Streams Supportโ€‹

SeaStreamer Redis provides a Kafka-like stream semantics:

  • Non-group streaming with AutoStreamReset option
  • Consumer-group-based streaming with auto-ack and/or auto-commit
  • Load balancing among consumers with automatic failover
  • Seek/rewind to point in time

You don't have to call XADD, XREAD, XACK, etc... anymore!

Enum-based generic interfaceโ€‹

The trait-based API requires you to designate the concrete Streamer type for monomorphization, otherwise the code cannot compile.

Akin to how SeaORM implements runtime-polymorphism, SeaStreamer provides a enum-based generic streamer, in which the backend is selected on runtime.

Here is an illustration (full example):

// sea-streamer-socket
pub struct SeaConsumer {
backend: SeaConsumerBackend,
}

enum SeaConsumerBackend {
#[cfg(feature = "backend-kafka")]
Kafka(KafkaConsumer),
#[cfg(feature = "backend-redis")]
Redis(RedisConsumer),
#[cfg(feature = "backend-stdio")]
Stdio(StdioConsumer),
}

// Your code
let uri: StreamerUri = "kafka://localhost:9092".parse()?; // or
let uri: StreamerUri = "redis://localhost:6379".parse()?; // or
let uri: StreamerUri = "stdio://".parse()?;

// SeaStreamer will be backed by Kafka, Redis or Stdio depending on the URI
let streamer = SeaStreamer::connect(uri, Default::default()).await?;

// Set backend-specific options
let mut options = SeaConsumerOptions::new(ConsumerMode::Resumable);
options.set_kafka_consumer_options(|options: &mut KafkaConsumerOptions| { .. });
options.set_redis_consumer_options(|options: &mut RedisConsumerOptions| { .. });
let mut consumer: SeaConsumer = streamer.create_consumer(stream_keys, options).await?;

// You can still retrieve the concrete type
let kafka: Option<&mut KafkaConsumer> = consumer.get_kafka();
let redis: Option<&mut RedisConsumer> = consumer.get_redis();

So you can "write once, stream anywhere"!

Good old unix pipeโ€‹

In SeaStreamer, stdin & stdout can be used as stream source and sink.

Say you are developing some processors to transform a stream in several stages:

./processor_1 --input kafka://localhost:9092/input --output kafka://localhost:9092/stage_1 &
./processor_2 --input kafka://localhost:9092/stage_1 --output kafka://localhost:9092/stage_2 &
./processor_3 --input kafka://localhost:9092/stage_2 --output kafka://localhost:9092/output &

It would be great if we can simply pipe the processors together right?

With SeaStreamer, you can do the following:

./processor_1 --input kafka://localhost:9092/input --output stdio:///stream |
./processor_2 --input stdio:///stream --output stdio:///stream |
./processor_3 --input stdio:///stream --output kafka://localhost:9092/output

All without recompiling the stream processors! Now, you can develop locally with the comfort of using |, >, < and your favourite unix program in the shell.

Testableโ€‹

SeaStreamer encourages you to write tests at all levels:

  • You can execute tests involving several stream processors in the same OS process
  • You can execute tests involving several OS processes by connecting them with pipes
  • You can execute tests involving several stream processors with Redis / Kafka

All against the same piece of code! Let SeaStreamer take away the boilerplate and mocking facility from your codebase.

Below is an example of intra-process testing, which can be run with cargo test without any dependency or side-effects:

let stream = StreamKey::new("test")?;
let mut options = StdioConnectOptions::default();
options.set_loopback(true); // messages produced will be feed back to consumers
let streamer = StdioStreamer::connect(StreamerUri::zero(), options).await?;
let producer = streamer.create_producer(stream.clone(), Default::default()).await?;
let mut consumer = streamer.create_consumer(&[stream.clone()], Default::default()).await?;

for i in 0..5 {
let mess = format!("{}", i);
producer.send(mess)?;
}

let seq = collect(&mut consumer, 5).await;
assert_eq!(seq, [0, 1, 2, 3, 4]);

Getting startedโ€‹

If you are eager to get started with SeaStreamer, you can checkout our set of examples:

  • consumer: A basic consumer
  • producer: A basic producer
  • processor: A basic stream processor
  • resumable: A resumable stream processor that continues from where it left off
  • buffered: An advanced stream processor with internal buffering and batch processing
  • blocking: An advanced stream processor for handling blocking / CPU-bound tasks

Read the official documentation to learn more.

Roadmapโ€‹

A few major components we plan to develop:

  • File Backend
  • Redis Cluster

We welcome you to join our Discussions if you have thoughts or ideas!

Peopleโ€‹

SeaStreamer is designed and developed by the same mind who brought you SeaORM:

Chris Tsang

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.


  1. except sea-streamer-stdio, but only contends on consumer add/dropโ†ฉ
- + \ No newline at end of file diff --git a/blog/2023-08-12-announcing-seaorm-0.12/index.html b/blog/2023-08-12-announcing-seaorm-0.12/index.html index 0442126f774..dc2348fac9f 100644 --- a/blog/2023-08-12-announcing-seaorm-0.12/index.html +++ b/blog/2023-08-12-announcing-seaorm-0.12/index.html @@ -10,14 +10,14 @@ - +
Skip to main content

Announcing SeaORM 0.12 ๐Ÿš

ยท 8 min read
SeaQL Team
SeaORM 0.12 Banner

๐ŸŽ‰ We are pleased to announce SeaORM 0.12 today!

We still remember the time when we first introduced SeaORM to the Rust community two years ago. We set out a goal to enable developers to build asynchronous database-driven applications in Rust.

Today, many open-source projects, a handful of startups and many more closed-source projects are using SeaORM. Thank you all who participated and contributed in the making!

SeaORM Star History

New Features ๐ŸŒŸโ€‹

๐Ÿงญ Seaography: GraphQL integration (preview)โ€‹

Seaography example

Seaography is a GraphQL framework built on top of SeaORM. In 0.12, Seaography integration is built into sea-orm. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a GraphQL server from SeaORM entities!

While Seaography development is still in an early stage, it is especially useful in prototyping and building internal-use admin panels.

Read the documentation to learn more.

Added macro DerivePartialModelโ€‹

#1597 Now you can easily perform custom select to query only the columns you needed

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "Cake")]
struct PartialCake {
name: String,
#[sea_orm(
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))"#
)]
name_upper: String,
}

assert_eq!(
cake::Entity::find()
.into_partial_model::<PartialCake>()
.into_statement(DbBackend::Sqlite)
.to_string(),
r#"SELECT "cake"."name", UPPER("cake"."name") AS "name_upper" FROM "cake""#
);

Added Select::find_with_linkedโ€‹

#1728, #1743 Similar to find_with_related, you can now select related entities and consolidate the models.

// Consider the following link
pub struct BakedForCustomer;

impl Linked for BakedForCustomer {
type FromEntity = Entity;

type ToEntity = super::customer::Entity;

fn link(&self) -> Vec<RelationDef> {
vec![
super::cakes_bakers::Relation::Baker.def().rev(),
super::cakes_bakers::Relation::Cake.def(),
super::lineitem::Relation::Cake.def().rev(),
super::lineitem::Relation::Order.def(),
super::order::Relation::Customer.def(),
]
}
}

let res: Vec<(baker::Model, Vec<customer::Model>)> = Baker::find()
.find_with_linked(baker::BakedForCustomer)
.order_by_asc(baker::Column::Id)
.all(db)
.await?

Added DeriveValueType derive macro for custom wrapper typesโ€‹

#1720 So now you can use newtypes easily.

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "custom_value_type")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub number: Integer,
// Postgres only
pub str_vec: StringVec,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct Integer(i32);

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct StringVec(pub Vec<String>);

Which saves you the boilerplate of:

impl std::convert::From<StringVec> for Value { .. }

impl TryGetable for StringVec {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I)
-> Result<Self, TryGetError> { .. }
}

impl ValueType for StringVec {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { .. }

fn type_name() -> String { "StringVec".to_owned() }

fn array_type() -> ArrayType { ArrayType::String }

fn column_type() -> ColumnType { ColumnType::String(None) }
}

Enhancements ๐Ÿ†™โ€‹

#1433 Chained AND / OR join ON conditionโ€‹

Added more macro attributes to DeriveRelation

// Entity file

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
// By default, it's `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
)]
TropicalFruit,
// Specify `condition_type = "any"` to override it, now it becomes
// `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
condition_type = "any",
)]
OrTropicalFruit,
}

#1508 Supports entity with composite primary key of arity 12โ€‹

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "primary_key_of_12")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id_1: String,
...
#[sea_orm(primary_key, auto_increment = false)]
pub id_12: bool,
}

#1677 Added UpdateMany::exec_with_returning()โ€‹

let models: Vec<Model> = Entity::update_many()
.col_expr(Column::Values, Expr::expr(..))
.exec_with_returning(db)
.await?;

#1511 Added MigratorTrait::migration_table_name() method to configure the name of migration tableโ€‹

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
// Override the name of migration table
fn migration_table_name() -> sea_orm::DynIden {
Alias::new("override_migration_table_name").into_iden()
}
...
}

#1707 Added DbErr::sql_err() method to parse common database errorsโ€‹

assert!(matches!(
cake.into_active_model().insert(db).await
.expect_err("Insert a row with duplicated primary key")
.sql_err(),
Some(SqlErr::UniqueConstraintViolation(_))
));

assert!(matches!(
fk_cake.insert(db).await
.expect_err("Insert a row with invalid foreign key")
.sql_err(),
Some(SqlErr::ForeignKeyConstraintViolation(_))
));

#1737 Introduced new ConnAcquireErrโ€‹

enum DbErr {
ConnectionAcquire(ConnAcquireErr),
..
}

enum ConnAcquireErr {
Timeout,
ConnectionClosed,
}

#1627 Added DatabaseConnection::ping()โ€‹

|db: DatabaseConnection| {
assert!(db.ping().await.is_ok());
db.clone().close().await;
assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire)));
}

#1708 Added TryInsert that does not panic on empty insertsโ€‹

// now, you can do:
let res = Bakery::insert_many(std::iter::empty())
.on_empty_do_nothing()
.exec(db)
.await;

assert!(matches!(res, Ok(TryInsertResult::Empty)));

#1712 Insert on conflict do nothing to return Okโ€‹

let on = OnConflict::column(Column::Id).do_nothing().to_owned();

// Existing behaviour
let res = Entity::insert_many([..]).on_conflict(on).exec(db).await;
assert!(matches!(res, Err(DbErr::RecordNotInserted)));

// New API; now you can:
let res =
Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await;
assert!(matches!(res, Ok(TryInsertResult::Conflicted)));

#1740, #1755 Replacing sea_query::Iden with sea_orm::DeriveIdenโ€‹

To provide a more consistent interface, sea-query/derive is no longer enabled by sea-orm, as such, Iden no longer works as a derive macro (it's still a trait).

// then:

#[derive(Iden)]
#[iden = "category"]
pub struct CategoryEnum;

#[derive(Iden)]
pub enum Tea {
Table,
#[iden = "AfternoonTea"]
EverydayTea,
}

// now:

#[derive(DeriveIden)]
#[sea_orm(iden = "category")]
pub struct CategoryEnum;

#[derive(DeriveIden)]
pub enum Tea {
Table,
#[sea_orm(iden = "AfternoonTea")]
EverydayTea,
}

New Release Train Ferry ๐Ÿšขโ€‹

It's been the 12th release of SeaORM! Initially, a major version was released every month. It gradually became 2 to 3 months, and now, it's been 6 months since the last major release. As our userbase grew and some are already using SeaORM in production, we understand the importance of having a stable API surface and feature set.

That's why we are committed to:

  1. Reviewing breaking changes with strict scrutiny
  2. Expanding our test suite to cover all features of our library
  3. Never remove features, and consider deprecation carefully

Today, the architecture of SeaORM is pretty solid and stable, and with the 0.12 release where we paid back a lot of technical debt, we will be able to deliver new features and enhancements without breaking. As our major dependency SQLx is not 1.0 yet, technically we cannot be 1.0.

We are still advancing rapidly, and we will always make a new release as soon as SQLx makes a new release, so that you can upgrade everything at once. As a result, the next major release of SeaORM will come out 6 months from now, or when SQLx makes a new release, whichever is earlier.

Community Survey ๐Ÿ“โ€‹

SeaQL is an independent open-source organization. Our goal is to enable developers to build data intensive applications in Rust. If you are using SeaORM, please participate in the SeaQL Community Survey!

By completing this survey, you will help us gather insights into how you, the developer, are using our libraries and identify means to improve your developer experience. We will also publish an annual survey report to summarize our findings.

If you are a happy user of SeaORM, consider writing us a testimonial!

A big thank to DigitalOcean who sponsored our server hosting, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Shane Sveller
ร‰mile Fugulin
Afonso Barracha
Jacob Trueb
Natsuki Ikeguchi
Marlon Mueller-Soppart
KallyDev
Dean Sheather
Manfred Lee
Roland Gorรกcz
IceApinan
Renรฉ Klaฤan
Unnamed Sponsor

What's Next for SeaORM? โ›ตโ€‹

Open-source project is a never-ending work, and we are actively looking for ways to sustain the project. You can support our endeavour by starring & sharing our repositories and becoming a sponsor.

We are considering multiple directions to generate revenue for the organization. If you have any suggestion, or want to join or collaborate with us, please contact us via hello[at]sea-ql.org.

Thank you for your support, and together we can make open-source sustainable.

- + \ No newline at end of file diff --git a/blog/2023-09-06-whats-new-in-sea-streamer-0.3/index.html b/blog/2023-09-06-whats-new-in-sea-streamer-0.3/index.html index 8e359232e2a..56290910bcc 100644 --- a/blog/2023-09-06-whats-new-in-sea-streamer-0.3/index.html +++ b/blog/2023-09-06-whats-new-in-sea-streamer-0.3/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

What's new in SeaStreamer 0.3

ยท 5 min read
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaStreamer 0.3.x!

File Backendโ€‹

A major addition in SeaStreamer 0.3 is the file backend. It implements the same high-level MPMC API, enabling streaming to and from files. There are different use cases. For example, it can be used to dump data from Redis / Kafka and process them locally, or as an intermediate file format for storage or transport.

The SeaStreamer File format, .ss is pretty simple. It's very much like .ndjson, but binary. The file format is designed with the following goals:

  1. Binary data support without encoding overheads
  2. Efficiency in rewinding / seeking through a large dump
  3. Streaming-friendliness - File can be truncated without losing integrity

Let me explain in details.

First of all, SeaStreamer File is a container format. It only concerns the message stream and framing, not the payload. It's designed to be paired with a binary message format like Protobuf or BSON.

Encode-freeโ€‹

JSON and CSV are great plain text file formats, but they are not binary friendly. Usually, to encode binary data, one would use base64. It therefore imposes an expensive encoding / decoding overhead. In a binary protocol, delimiters are frequently used to signal message boundaries. As a consequence, byte stuffing is needed to escape the bytes.

In SeaStreamer, we want to avoid the encoding overhead entirely. The payload should be written to disk verbatim. So the file format revolves around constructing message frames and placing checksums to ensure that data is interpreted correctly.

Efficient seekโ€‹

A delimiter-based protocol has an advantage: the byte stream can be randomly sought, and we always have no trouble reading the next message.

Since SeaStreamer does not rely on delimiters, we can't easily align to message frames after a random seek. We solve this problem by placing beacons in a regular interval at fixed locations throughout the file. E.g. say the beacon interval is 1024, there will be a beacon at the 1024th byte, the 2048th, and so on. Then, every time we want to seek to a random location, we'd seek to the closest N * 1024 byte and read from there.

These beacons also double as indices: they contain summaries of the individual streams. So given a particular stream key and sequence number (or timestamp) to search for, SeaStreamer can quickly locate the message just by reading the beacons. It doesn't matter if the stream's messages are sparse!

Streaming-friendlinessโ€‹

It should always be safe to truncate files. It should be relatively easy to split a file into chunks. We should be able to tell if the data is corrupted.

SeaStreamer achieves this by computing a checksum for every message, and also the running checksum of the checksums for each stream. It's not enforced right now, but in theory we can detect if any messages are missing from a stream.

Summaryโ€‹

This file format is also easy to implement in different languages, as we just made an (experimental) reader in Typescript.

That's it! If you are interested, you can go and take a look at the format description.

Redis Backendโ€‹

Redis Streams are underrated! They have high throughput and concurrency, and are best suited for non-persistent stream processing near or on the same host as the application.

The obstacle is probably in library support. Redis Streams' API is rather low level, and there aren't many high-level libraries to help with programming, as opposed to Kafka, which has versatile official programming libraries.

The pitfall is, it's not easy to maximize concurrency with the raw Redis API. To start, you'd need to pipeline XADD commands. You'd also need to time and batch XACKs so that it does not block reads and computation. And of course you want to separate the reads and writes on different threads.

SeaStreamer breaks these obstacles for you and offers a Kafka-like API experience!

Benchmarkโ€‹

In 0.3, we have done some optimizations to improve the throughput of the Redis and File backend. We set our initial benchmark at 100k messages per second, which hopefully we can further improve over time.

Our micro benchmark involves a simple program producing or consuming 100k messages, where each message has a payload of 256 bytes.

For Redis, it's running on the same computer in Docker. On my not-very-impressive laptop with a 10th Gen Intel Core i7, the numbers are somewhat around:

Producerโ€‹

redis    0.5s
stdio 0.5s
file 0.5s

Consumerโ€‹

redis    1.0s
stdio 1.0s
file 1.1s

It practically means that we are comfortably in the realm of producing 100k messages per second, but are just about able to consume 100k messages in 1 second. Suggestions to performance improvements are welcome!

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.

- + \ No newline at end of file diff --git a/blog/2023-11-22-async-runtime-generic/index.html b/blog/2023-11-22-async-runtime-generic/index.html index 8652a9929a8..ed5005b2bec 100644 --- a/blog/2023-11-22-async-runtime-generic/index.html +++ b/blog/2023-11-22-async-runtime-generic/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

Writing Async Runtime Generic Library

ยท 4 min read
Chris Tsang

If you are writing an async application in Rust, at some point you'd want to separate the code into several crates. There are some benefits:

  1. Better encapsulation. Having a crate boundary between sub-systems can lead to cleaner code and a more well-defined API. No more pub(crate)!
  2. Faster compilation. By breaking down a big crate into several independent small crates, they can be compiled concurrently.

But the question is, if you are using only one async runtime anyway, what are the benefits of writing async-runtime-generic libraries?

  1. Portability. You can easily switch to a different async runtime, or wasm.
  2. Correctness. Testing a library against both tokio and async-std can uncover more bugs, including concurrency bugs (due to fuzzy task execution orders) and "undefined behaviour" either due to misunderstanding or async-runtime implementation details

So now you've decided to write async-runtime-generic libraries! Here I want to share 3 strategies along with examples found in the Rust ecosystem.

Approach 1: Defining your own AsyncRuntime traitโ€‹

Using the futures crate you can write very generic library code, but there is one missing piece: time - to sleep or timeout, you have to rely on an async runtime. If that's all you need, you can define your own AsyncRuntime trait and requires downstream to implement it. This is the approach used by rdkafka:

pub trait AsyncRuntime: Send + Sync + 'static {
type Delay: Future<Output = ()> + Send;

/// It basically means the return value must be a `Future`
fn sleep(duration: Duration) -> Self::Delay;
}

Here is how it's implemented:

impl AsyncRuntime for TokioRuntime {
type Delay = tokio::time::Sleep;

fn sleep(duration: Duration) -> Self::Delay {
tokio::time::sleep(duration)
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>() {
R::sleep(Duration::from_millis(1)).await;
}

Approach 2: Abstract the async runtimes internally and expose feature flagsโ€‹

This is the approach used by redis-rs.

To work with network connections or file handle, you can use the AsyncRead / AsyncWrite traits:

#[async_trait]
pub(crate) trait AsyncRuntime: Send + Sync + 'static {
type Connection: AsyncRead + AsyncWrite + Send + Sync + 'static;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection>;
}

Then you'll define a module for each async runtime:

#[cfg(feature = "runtime-async-std")]
mod async_std_impl;
#[cfg(feature = "runtime-async-std")]
use async_std_impl::*;

#[cfg(feature = "runtime-tokio")]
mod tokio_impl;
#[cfg(feature = "runtime-tokio")]
use tokio_impl::*;

Where each module would look like:

tokio_impl.rs
#[async_trait]
impl AsyncRuntime for TokioRuntime {
type Connection = tokio::net::TcpStream;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection> {
tokio::net::TcpStream::connect(addr).await
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>(conn: R::Connection) {
conn.write(b"some bytes").await;
}

Approach 3: Maintain an async runtime abstraction crateโ€‹

This is the approach used by SQLx and SeaStreamer.

Basically, aggregate all async runtime APIs you'd use and write a wrapper library. This may be tedious, but this also has the benefit of specifying all interactions with the async runtime in one place for your project, which could be handy for debugging or tracing.

For example, async Task handling:

common-async-runtime/tokio_task.rs
pub use tokio::task::{JoinHandle as TaskHandle};

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
tokio::task::spawn(future)
}

async-std's task API is slightly different (in tokio the output is Result<T, JoinError>), which requires some boilerplate:

common-async-runtime/async_std_task.rs
/// A shim to match tokio's API
pub struct TaskHandle<T>(async_std::task::JoinHandle<T>);

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
TaskHandle(async_std::task::spawn(future))
}

#[derive(Debug)]
pub struct JoinError;

impl std::error::Error for JoinError {}

// This is basically how you wrap a `Future`
impl<T> Future for TaskHandle<T> {
type Output = Result<T, JoinError>;

fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.0.poll_unpin(cx) {
std::task::Poll::Ready(res) => std::task::Poll::Ready(Ok(res)),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}

In the library's Cargo.toml, you can simply include common-async-runtime as dependency. This makes your library code 'pure', because now selecting an async runtime is controlled by downstream. Similar to approach 1, this crate can be compiled without any async runtime, which is neat!

Conclusionโ€‹

Happy hacking! Welcome to share your experience with the community.

- + \ No newline at end of file diff --git a/blog/2023-11-25-openuk-award/index.html b/blog/2023-11-25-openuk-award/index.html index bdc821316a8..74a4911b309 100644 --- a/blog/2023-11-25-openuk-award/index.html +++ b/blog/2023-11-25-openuk-award/index.html @@ -10,13 +10,13 @@ - +
Skip to main content

OpenUK Award 2023 ๐Ÿ†

ยท One min read
SeaQL Team

It is our honour to have been awarded by OpenUK for the 2023 Award in the Software category! The award ceremony was a very memorable experience. A huge thanks to Red Badger who sponsored the software award.

In 2023, we released SeaStreamer, two major versions of SeaORM, a new version of Seaography, and have been busy working on a new project on the side.

We reached the milestone of 5k GitHub stars and 2M crates.io downloads mid-year.

In the summer, we took in two interns for our 3rd summer of code.

We plan to offer internships tailored to UK students in 2024 through university internship programs. As always, we welcome contributors from all over the world, and may be we will enrol on GSoC 2024 again. (but open-source is not bounded any schedule, so you can start contributing anytime)

A big thanks to our sponsors who continued to support us, and we look forward to a more impactful 2024.

- + \ No newline at end of file diff --git a/blog/2024-01-18-community-survey-2023/index.html b/blog/2024-01-18-community-survey-2023/index.html index dac0a1a3e07..047241c9d9a 100644 --- a/blog/2024-01-18-community-survey-2023/index.html +++ b/blog/2024-01-18-community-survey-2023/index.html @@ -10,7 +10,7 @@ - + @@ -19,7 +19,7 @@ From these responses we hope to get an understanding of where the SeaQL and Rust community stands in 2023.

This is our first community survey, we will conduct the survey annually to keep track of how the community evolves over time.

Demographicsโ€‹

Q. Where are you located in?โ€‹

Participants are from 41 countries across the world!

Other: ArgentinaAustraliaAustriaBelarusBelgiumCyprusCzechiaDenmarkHungaryIranIrelandItalyJapanKazakstanKoreaMongoliaNigeriaNorwayPeruPolandSlovakiaSouth AfricaSpainSwedenTaiwan ThailandTurkeyUkraine

Use of SeaQL Librariesโ€‹

Q. Are you using SeaQL libraries in building a project?โ€‹

Q. Which SeaQL libraries are you using in building a project?โ€‹

Other: SeaographySeaStreamer

Q. Are you using SeaQL libraries in a personal, academic or professional context?โ€‹

Q. Why did you choose SeaQL libraries?โ€‹

Other: Async support, future proof and good documentationGood Query PerformanceIt was recommended on websites and YouTubeDoes not use SQL for migrationsBeginner-friendly and easy to get startedEasy to translate from Eloquent ORM knowledgeCan drop in to SeaQuery if necessaryI started with SQLx, then tried SeaQueryI found good examples on YouTube

Q. What qualities of SeaQL libraries do you think are important?โ€‹

Other: Simple SyntaxBeing able to easily express what you would otherwise be able to write in pure SQLMigration and entity generationClarify of the implementation and usage patternsEfficient query building especially with relations and joinsErgonomic API

Team & Project Natureโ€‹

Q. How many team members (including you) are working on the project?โ€‹

Q. Can you categorize the nature of the project?โ€‹

Other: ForecastingFinancial tradingEnterprise Resource Planning (ERP)FintechCloud infrstructure automationBackend for desktop, mobile and web application

Tech Stackโ€‹

Q. What is your development environment?โ€‹

Linux Breakdownโ€‹

Windows Breakdownโ€‹

macOS Breakdownโ€‹

Q. Which database(s) do you use?โ€‹

Q. Which web framework are you using?โ€‹

Q. What is the deployment environment?โ€‹

Rust at Workโ€‹

Q. Are you using Rust at work?โ€‹

Q. Which industry your company is in?โ€‹

Vague description of the companyโ€‹

A banking companyA business to business lending platformA cloud StorageA consulting companyA cybersecurity management platformAn IT solution companyAn E-Commerce clothing storeA children entertainmets companyA factory construction management platformA fintech startupA geology technology companyA publicly traded health-tech companyA private restaurant chainAn industrial IoT for heating and water distributionsAn internet providerA nonprofit tech research organizationA payment service providerA road intelligence companyA SaaS startupA server hosting providerA DevOps platform that helps our users scale their Kubernetes applicationAn Automotive company

Q. What is the size of your company?โ€‹

Q. How many engineers in your company are dedicated to writing Rust?โ€‹

Q. Which layer(s) of the technology stack are using Rust?โ€‹

Learning Rustโ€‹

Q. Are you learning / new to Rust?โ€‹

Q. Which language(s) are you most familiar with?โ€‹

Q. Are you familiar with SQL?โ€‹

Q. Do you find Rust easy or hard to learn?โ€‹

Q. What motivates you to learn Rust?โ€‹

Other: Ability to develop fast, secure and standalone API driven toolsEfficiency, safety, low resource usageGood design decisions from the startReliability and ease of developmentSchool makes me to learnRust is too coolThe ecosystem of libraries + general competence of lib authorsIt is the most loved languageThe guarantees Rust providesLearning something newType safety and speedWant to get away from NULLNo boilerplate, if you do not want itPerformance

Q. What learning resources do you rely on?โ€‹

Other: YouTubeOnline CoursesChatGPT

Q. What is your first project built using Rust?โ€‹

Other: ChatbotScraperRasterization of the mandelbrot setIoTLibrary

What's Nextโ€‹

Q. Which aspects do you want to see advancement on SeaORM?โ€‹

Thank you for all the suggestions, we will certainly take them into account!

Other: Full MySQL coverageMS SQL Server supportStructured queries for complex joinsA stable releaseData seedingMigrations based on Entity diffsType safetySupport tables without primary keyTurso integrationFetching nested structuresViews

Q. What tools would you be interested in using, if developed first-party by SeaQL?โ€‹

Other: An API integration testing utilityAn oso-based authorization integrationA visual tool for managing migrationsDatabase layout editor (like dbdiagram.io)

Share Your Thoughtsโ€‹

Q. Anything else you want to say?โ€‹

Didn't expect this section to turn into a testimonial, thank you for all the kind words :)

Good job yall

Great projects, thanks for your hard work

I expect it to be an asynchronous type-safe library. Keep up the good work!

I'd like to see entity generation without a database

The website, support from JetBrains, the documentation and the release cycle are very nice!

I'm very interested in how SeaORM will continue evolving and I would like to wish you the best of luck!

I've found SeaORM very useful and I'm very grateful to the development team for creating and maintaining it!

In TypeORM I can write entities and then generate migration from them. It's very handy. It helps to increase development speed. It would be nice to have this functionality in SeaORM.

It needs to have better integration with SeaQuery, I sometimes need to get to it because not all features are available in SeaORM which makes it a pain.

Keep the good work!

Keep going! Love SeaORM!

Keep up the great work. Rust needs a fast, ergonomic and reliable ORM.

SeaORM is very powerful, but the rust docs and tutorial examples could be more fleshed out.

SeaORM is an awesome library. Most things are quite concise and therefore straightforward. Simply a few edge cases concerning DB specific types and values could be better.

The trait system is too complex and coding experience is not pretty well with that.

Automatic migration generation would make the library pretty much perfect in my opinion.

SeaQL tutorials could be better. Much more detailed explanation and definitely it has to have best practices section for Design Patterns like and good best practices related with clean architecture.

SeaQL are great products and itโ€™s very enjoyable using them

Thank you <3

Thank you for awesome library!

Thank you for this wonderful project. I feel the documentation lacks examples for small functions and usage of some obscure features.

Thank you for your hard work!

Thank you for your work on SeaQL, your efforts are appreciated

Thank you for your work, we are seeking actively to include SeaORM in our projects

Thank you very much for your work!

Thanks a lot for the amazing work you guys put into this set of libraries. This is an amazing development for the rust ecosystem.

Thanks and keep up the good work.

Thanks for a great tool!

Thanks for all the amazing work.

Thanks for making SeaORM!

The project I am doing for work is only a prototype, it's a new implementation of a current Python forecasting project which uses a pandas and a custom psycopg2 orm. My intent is to create a faster/dev friendly version with SeaORM and Polars. I am hoping to eventually get a prototype I can display to my team to get a go ahead to fully develop a new version, and to migrate 4-5 other forecasting apps using shared libraries for io and calculations.

I have also been using SeaORM for a small API client for financial data, which I may make open source.

I think one thing which could really improve SeaORM is some more advance examples in the documentation section. The docs are really detailed as far as rust documentation goes.

Very promising project, keep it up.

Thank you so much for taking it upon yourselves to selflessly give your free time. It probably doesn't matter much, but thank you so much for your work. SeaORM is a fantastic tool that I can see myself using for a long time to come. I hope to make contributions in any form when I am under better circumstances :3 Kudos to the team!

ไฝ ไปฌ็š„ๅบ“้žๅธธ็š„ๆฃ’๏ผŒ่‡ณๅฐ‘ๆˆ‘่ง‰ๅพ—ๆฏ”Dieselๅฅฝๅคชๅคšไบ†๏ผŒๅ…ฅ้—จ็ฎ€ๅ•๏ผŒๅฏนๆ–ฐๆ‰‹้žๅธธๅ‹ๅฅฝ๏ผŒ่ฟ™ๆ˜ฏๆœ€ๅคง็š„ไบฎ็‚น๏ผŒๅ…ถๆฌกๆ˜ฏๅบ“่ฒŒไผผๅฏไปฅๅฎž็Žฐๅพˆๅคๆ‚็š„Join SQL้€ป่พ‘่€Œไธ็”จๅ†™ๅŽŸ็”Ÿ็š„SQL๏ผŒ่ฟ™็‚นไนŸๆ˜ฏ้žๅธธๅ€ผๅพ—็‚น่ตž็š„๏ผŒไฝ†ๆ˜ฏๅœจ่ฟ™ๅ—็š„ๆ–‡ๆกฃ่ฒŒไผผๅ†™็š„ๆœ‰็‚น็ฎ€็•ฅไบ†๏ผŒๅธŒๆœ›ๅฏไปฅไธฐๅฏŒไธ€ไธ‹ๆ–‡ๆกฃๅ†…ๅฎน๏ผŒๅฏนไบŽๅคๆ‚ๆŸฅ่ฏข็š„่ฏดๆ˜Žๅฏไปฅๆ›ดๅŠ ่ฏฆ็ป†ไธ€ไบ›๏ผŒ่ฟ™ๆ ทๅฐฑๅ†ๅฅฝไธ่ฟ‡ไบ†ใ€‚่ฐข่ฐขไฝ ไปฌ๏ผŒๆˆ‘ไผšๆŒ็ปญๅ…ณๆณจไฝ ไปฌ๏ผŒๆœชๆฅ็š„้กน็›ฎๅฆ‚ๆžœๆถ‰ๅŠORM๏ผŒ้‚ฃ็ปๅฏน้žไฝ ไปฌ่Žซๅฑžไบ†๏ผ

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL
- + \ No newline at end of file diff --git a/blog/2024-01-23-whats-new-in-seaorm-0.12.x/index.html b/blog/2024-01-23-whats-new-in-seaorm-0.12.x/index.html index 9a6ed254ca6..f0132289966 100644 --- a/blog/2024-01-23-whats-new-in-seaorm-0.12.x/index.html +++ b/blog/2024-01-23-whats-new-in-seaorm-0.12.x/index.html @@ -10,15 +10,15 @@ - +
-
Skip to main content

What's new in SeaORM 0.12.x

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. +

What's new in SeaORM 0.12.x

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL
- + \ No newline at end of file diff --git a/blog/404.html b/blog/404.html index def930984a1..5b713c21838 100644 --- a/blog/404.html +++ b/blog/404.html @@ -10,13 +10,13 @@ - +

Page Not Found

We could not find what you were looking for.

Please contact the owner of the site that linked you to the original URL and let them know their link is broken.

- + \ No newline at end of file diff --git a/blog/about-us/index.html b/blog/about-us/index.html index b2219ec9bca..59d86c5c719 100644 --- a/blog/about-us/index.html +++ b/blog/about-us/index.html @@ -10,13 +10,13 @@ - +

About Us

We are a small team of passionate Rust engineers. Seeing the potential of Rust to become one of the most important languages of our era, we sought to improve the data engineering toolings to make it a reality.

Rust is not just a programming language. It's a set of open-source values, an approach to software engineering, an ecosystem for libraries, but most importantly, a community for programmers like us to collaborate and thrive.

If you decided to develop software in Rust, that piece of software must be important to you, and other people. So much so you are willing to design it delicately and maintain it long term.

If you decided to contribute to Rust, then you truely believe in that sharing knowledge and experience will benefit others in the community and in turn ourselves.

Welcome to the community! You are also one of us.

- + \ No newline at end of file diff --git a/blog/archive/index.html b/blog/archive/index.html index 92d5e038405..b636bc963a5 100644 --- a/blog/archive/index.html +++ b/blog/archive/index.html @@ -10,13 +10,13 @@ - +

Archive

Archive

- + \ No newline at end of file diff --git a/blog/assets/js/97592684.a164a4d8.js b/blog/assets/js/97592684.a164a4d8.js new file mode 100644 index 00000000000..3167df4af99 --- /dev/null +++ b/blog/assets/js/97592684.a164a4d8.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunksea_ql_blog=self.webpackChunksea_ql_blog||[]).push([[8193],{9680:(e,a,t)=>{t.d(a,{Zo:()=>p,kt:()=>d});var r=t(6687);function n(e,a,t){return a in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function o(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);a&&(r=r.filter((function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var a=1;a=0||(n[t]=e[t]);return n}(e,a);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}var l=r.createContext({}),m=function(e){var a=r.useContext(l),t=a;return e&&(t="function"==typeof e?e(a):i(i({},a),e)),t},p=function(e){var a=m(e.components);return r.createElement(l.Provider,{value:a},e.children)},c={inlineCode:"code",wrapper:function(e){var a=e.children;return r.createElement(r.Fragment,{},a)}},u=r.forwardRef((function(e,a){var t=e.components,n=e.mdxType,o=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=m(t),d=n,h=u["".concat(l,".").concat(d)]||u[d]||c[d]||o;return t?r.createElement(h,i(i({ref:a},p),{},{components:t})):r.createElement(h,i({ref:a},p))}));function d(e,a){var t=arguments,n=a&&a.mdxType;if("string"==typeof e||n){var o=t.length,i=new Array(o);i[0]=u;var s={};for(var l in a)hasOwnProperty.call(a,l)&&(s[l]=a[l]);s.originalType=e,s.mdxType="string"==typeof e?e:n,i[1]=s;for(var m=2;m{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>m});var r=t(1308),n=(t(6687),t(9680));const o={slug:"2024-01-23-whats-new-in-seaorm-0.12.x",title:"What's new in SeaORM 0.12.x",author:"SeaQL Team",author_title:"Chris Tsang",author_url:"https://github.com/SeaQL",author_image_url:"https://www.sea-ql.org/blog/img/SeaQL.png",tags:["news"]},i=void 0,s={permalink:"/blog/2024-01-23-whats-new-in-seaorm-0.12.x",editUrl:"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md",source:"@site/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md",title:"What's new in SeaORM 0.12.x",description:"It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!",date:"2024-01-23T00:00:00.000Z",formattedDate:"January 23, 2024",tags:[{label:"news",permalink:"/blog/tags/news"}],readingTime:6.89,hasTruncateMarker:!1,authors:[{name:"SeaQL Team",title:"Chris Tsang",url:"https://github.com/SeaQL",imageURL:"https://www.sea-ql.org/blog/img/SeaQL.png"}],frontMatter:{slug:"2024-01-23-whats-new-in-seaorm-0.12.x",title:"What's new in SeaORM 0.12.x",author:"SeaQL Team",author_title:"Chris Tsang",author_url:"https://github.com/SeaQL",author_image_url:"https://www.sea-ql.org/blog/img/SeaQL.png",tags:["news"]},nextItem:{title:"SeaQL Community Survey 2023 Results",permalink:"/blog/2024-01-18-community-survey-2023"}},l={authorsImageUrls:[void 0]},m=[{value:"Celebrating 2M downloads on crates.io \ud83d\udce6",id:"celebrating-2m-downloads-on-cratesio-",level:2},{value:"New Features",id:"new-features",level:2},{value:"Entity format update",id:"entity-format-update",level:3},{value:"Cursor paginator improvements",id:"cursor-paginator-improvements",level:3},{value:"Added "proxy" to database backend",id:"added-proxy-to-database-backend",level:3},{value:"Enhancements",id:"enhancements",level:2},{value:"Bug fixes",id:"bug-fixes",level:2},{value:"Upgrades",id:"upgrades",level:2},{value:"House Keeping",id:"house-keeping",level:2},{value:"Release planning",id:"release-planning",level:2},{value:"Sponsor",id:"sponsor",level:2},{value:"Gold Sponsors",id:"gold-sponsors",level:4},{value:"Sponsors",id:"sponsors",level:4},{value:"Rustacean Sticker Pack \ud83e\udd80",id:"rustacean-sticker-pack-",level:2}],p={toc:m};function c(e){let{components:a,...t}=e;return(0,n.kt)("wrapper",(0,r.Z)({},p,t,{components:a,mdxType:"MDXLayout"}),(0,n.kt)("img",{alt:"SeaORM 0.12 Banner",src:"/blog/img/SeaORM%2012%20Banner.png"}),(0,n.kt)("p",null,"It had been a while since the initial ",(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/"},"SeaORM 0.12 release"),". This blog post summarizes the new features and enhancements introduced in SeaORM ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/releases/tag/0.12.2"},(0,n.kt)("inlineCode",{parentName:"a"},"0.12.2"))," through ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/releases/tag/0.12.12"},(0,n.kt)("inlineCode",{parentName:"a"},"0.12.12")),"!"),(0,n.kt)("h2",{id:"celebrating-2m-downloads-on-cratesio-"},"Celebrating 2M downloads on crates.io \ud83d\udce6"),(0,n.kt)("p",null,"We've just reached the milestone of 2,000,000 all time downloads on ",(0,n.kt)("a",{parentName:"p",href:"https://crates.io/crates/sea-orm"},"crates.io"),". It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community."),(0,n.kt)("h2",{id:"new-features"},"New Features"),(0,n.kt)("h3",{id:"entity-format-update"},"Entity format update"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1898"},"#1898")," Add support for root JSON arrays (requires the ",(0,n.kt)("inlineCode",{parentName:"li"},"json-array")," / ",(0,n.kt)("inlineCode",{parentName:"li"},"postgres-array")," feature)! It involved an intricate type system refactor to work around the orphan rule.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "json_struct_vec")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n #[sea_orm(column_type = "Json")]\n pub struct_vec: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]\npub struct JsonColumn {\n pub value: String,\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2009"},"#2009")," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"comment")," attribute for Entity; ",(0,n.kt)("inlineCode",{parentName:"li"},"create_table_from_entity")," now supports comment on MySQL")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "applog", comment = "app logs")]\npub struct Model {\n #[sea_orm(primary_key, comment = "ID")]\n pub id: i32,\n #[sea_orm(comment = "action")]\n pub action: String,\n pub json: Json,\n pub created_at: DateTimeWithTimeZone,\n}\n')),(0,n.kt)("h3",{id:"cursor-paginator-improvements"},"Cursor paginator improvements"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2037"},"#2037")," Added descending order to Cursor:")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},"// (default behaviour) Before 5 ASC, i.e. id < 5\n\nlet mut cursor = Entity::find().cursor_by(Column::Id);\ncursor.before(5);\n\nassert_eq!(\n cursor.first(4).all(db).await?,\n [\n Model { id: 1 },\n Model { id: 2 },\n Model { id: 3 },\n Model { id: 4 },\n ]\n);\n\n// (new API) After 5 DESC, i.e. id < 5\n\nlet mut cursor = Entity::find().cursor_by(Column::Id);\ncursor.after(5).desc();\n\nassert_eq!(\n cursor.first(4).all(db).await?,\n [\n Model { id: 4 },\n Model { id: 3 },\n Model { id: 2 },\n Model { id: 1 },\n ]\n);\n")),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1826"},"#1826")," Added cursor support to ",(0,n.kt)("inlineCode",{parentName:"li"},"SelectTwo"),":")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},"// Join with linked relation; cursor by first table's id\n\ncake::Entity::find()\n .find_also_linked(entity_linked::CakeToFillingVendor)\n .cursor_by(cake::Column::Id)\n .before(10)\n .first(2)\n .all(&db)\n .await?\n\n// Join with relation; cursor by the 2nd table's id \n\ncake::Entity::find()\n .find_also_related(Fruit)\n .cursor_by_other(fruit::Column::Id)\n .before(10)\n .first(2)\n .all(&db)\n .await?\n")),(0,n.kt)("h3",{id:"added-proxy-to-database-backend"},'Added "proxy" to database backend'),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/pull/1881"},"#1881"),", ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/pull/2000"},"#2000"),' Added "proxy" to database backend (requires feature flag ',(0,n.kt)("inlineCode",{parentName:"p"},"proxy"),")."),(0,n.kt)("p",null,"It enables the possibility of using SeaORM on edge / client-side! See the ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/tree/master/examples/proxy_gluesql_example"},"GlueSQL demo")," for an example."),(0,n.kt)("h2",{id:"enhancements"},"Enhancements"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1954"},"#1954")," ","[sea-orm-macro]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"#[sea_orm(skip)]")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"FromQueryResult")," derive macro")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]\npub struct PublicUser {\n pub id: i64,\n pub name: String,\n #[serde(skip_serializing_if = "Vec::is_empty")]\n #[sea_orm(skip)]\n pub something: Something,\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1598"},"#1598")," ","[sea-orm-macro]"," Added support for Postgres arrays in ",(0,n.kt)("inlineCode",{parentName:"li"},"FromQueryResult")," impl of ",(0,n.kt)("inlineCode",{parentName:"li"},"JsonValue"))),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'// existing API:\n\nassert_eq!(\n Entity::find_by_id(1).one(db).await?,\n Some(Model {\n id: 1,\n name: "Collection 1".into(),\n integers: vec![1, 2, 3],\n teas: vec![Tea::BreakfastTea],\n colors: vec![Color::Black],\n })\n);\n\n// new API:\n\nassert_eq!(\n Entity::find_by_id(1).into_json().one(db).await?,\n Some(json!({\n "id": 1,\n "name": "Collection 1",\n "integers": [1, 2, 3],\n "teas": ["BreakfastTea"],\n "colors": [0],\n }))\n);\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1828"},"#1828")," ","[sea-orm-migration]"," Check if an index exists")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'use sea_orm_migration::prelude::*;\n#[derive(DeriveMigrationName)]\npub struct Migration;\n#[async_trait::async_trait]\nimpl MigrationTrait for Migration {\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {\n // ...\n\n // Make sure the index haven\'t been created\n assert!(!manager.has_index("cake", "cake_name_index").await?);\n\n manager\n .create_index(\n Index::create()\n .name("cake_name_index")\n .table(Cake::Table)\n .col(Cake::Name)\n .to_owned(),\n )\n .await?;\n\n Ok(())\n }\n\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {\n // ...\n }\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2030"},"#2030")," Improve query performance of ",(0,n.kt)("inlineCode",{parentName:"li"},"Paginator"),"'s ",(0,n.kt)("inlineCode",{parentName:"li"},"COUNT")," query"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2055"},"#2055")," Added SQLx slow statements logging to ",(0,n.kt)("inlineCode",{parentName:"li"},"ConnectOptions")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1867"},"#1867")," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"QuerySelect::lock_with_behavior")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2002"},"#2002")," Cast enums in ",(0,n.kt)("inlineCode",{parentName:"li"},"is_in")," and ",(0,n.kt)("inlineCode",{parentName:"li"},"is_not_in")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1999"},"#1999")," Add source annotations to errors"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/issues/1960"},"#1960")," Implement ",(0,n.kt)("inlineCode",{parentName:"li"},"StatementBuilder")," for ",(0,n.kt)("inlineCode",{parentName:"li"},"sea_query::WithQuery")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1979"},"#1979")," Added method ",(0,n.kt)("inlineCode",{parentName:"li"},"expr_as_")," that accepts ",(0,n.kt)("inlineCode",{parentName:"li"},"self")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1868"},"#1868")," Loader: use ",(0,n.kt)("inlineCode",{parentName:"li"},"ValueTuple")," as hash key"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1934"},"#1934")," ","[sea-orm-cli]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"--enum-extra-derives")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1952"},"#1952")," ","[sea-orm-cli]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"--enum-extra-attributes")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1693"},"#1693")," ","[sea-orm-cli]"," Support generation of related entity with composite foreign key")),(0,n.kt)("h2",{id:"bug-fixes"},"Bug fixes"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1855"},"#1855"),", ",(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2054"},"#2054")," ","[sea-orm-macro]"," Qualify types in ",(0,n.kt)("inlineCode",{parentName:"li"},"DeriveValueType")," macro"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1953"},"#1953")," ","[sea-orm-cli]"," Fix duplicated active enum use statements on generated entities"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1821"},"#1821")," ","[sea-orm-cli]"," Fix entity generation for non-alphanumeric enum variants"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2071"},"#2071")," ","[sea-orm-cli]"," Fix entity generation for relations with composite keys"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/issues/1800"},"#1800")," Fixed ",(0,n.kt)("inlineCode",{parentName:"li"},"find_with_related")," consolidation logic"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/commit/5a6acd67312601e4dba32896600044950e20f99f"},"5a6acd67")," Fixed ",(0,n.kt)("inlineCode",{parentName:"li"},"Loader")," panic on empty inputs")),(0,n.kt)("h2",{id:"upgrades"},"Upgrades"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1984"},"#1984")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"axum")," example to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.7")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1858"},"#1858")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"chrono")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.4.30")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1959"},"#1959")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"rocket")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.5.0")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"sea-query")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.30.5")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"sea-schema")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.14.2")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"salvo")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.50"))),(0,n.kt)("h2",{id:"house-keeping"},"House Keeping"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2057"},"#2057")," Fix clippy warnings on 1.75"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1811"},"#1811")," Added test cases for ",(0,n.kt)("inlineCode",{parentName:"li"},"find_xxx_related/linked"))),(0,n.kt)("h2",{id:"release-planning"},"Release planning"),(0,n.kt)("p",null,"In the ",(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/"},"announcement blog post")," of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!"),(0,n.kt)("p",null,"There are still a few breaking changes planned for the next major release. After some ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/discussions/2031"},"discussions")," and consideration, we decided that the next major release will be a release candidate for 1.0!"),(0,n.kt)("h2",{id:"sponsor"},"Sponsor"),(0,n.kt)("p",null,"A big thank to ",(0,n.kt)("a",{parentName:"p",href:"https://www.digitalocean.com/"},"DigitalOcean")," who sponsored our servers, and ",(0,n.kt)("a",{parentName:"p",href:"https://www.jetbrains.com/"},"JetBrains")," who sponsored our IDE, and every sponsor on ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/sponsors/SeaQL"},"GitHub Sponsor"),"!"),(0,n.kt)("p",null,"If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization."),(0,n.kt)("p",null,"A big shout out to our sponsors \ud83d\ude07:"),(0,n.kt)("h4",{id:"gold-sponsors"},"Gold Sponsors"),(0,n.kt)("a",{href:"https://www.digitalocean.com/"},(0,n.kt)("img",{src:"https://www.sea-ql.org/static/sponsors/Osmos.svg#light",width:"238"}),(0,n.kt)("img",{src:"https://www.sea-ql.org/static/sponsors/Osmos-dark.svg#dark",width:"238"})),(0,n.kt)("h4",{id:"sponsors"},"Sponsors"),(0,n.kt)("div",{class:"row"},(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Sytten"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/2366731?u=2f43900772265deac96eb7a816d28a5a48b9a8dd&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"\xc9mile Fugulin")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/tugascript"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/64930104?u=1171ed4ccb6da73b52de274109077686290da3a5&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Afonso Barracha")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/shanesveller"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/831?u=474c7b31ddf0a5c1a03d1142dd18a300279c644a&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Shane Sveller")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/deansheather"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/11241812?u=260538c7d8b8c3c5350dba175ebb8294358441e0&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Dean Sheather")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/marcusbuffett"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/1834328?u=fd066d99cf4a6333bfb3927d1c756af4bb8baf7e&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Marcus Buffett")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/reneklacan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/1935686?u=132be985351312fcf96999daef515f551a93bb0d&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Ren\xe9 Kla\u010dan")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Iceapinan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/2698243?u=a852c75ac10098b9980f57af298be1399f6de66b&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"IceApinan")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/trueb2"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/8592049?u=031c9ee96b47c27e3a8c485c3c0ebcd4f96120c9&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Jacob Trueb")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/ktanaka101"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/10344925?u=a96d92e7cdd73f774b35fd0bc998964c07b24e29&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Kentaro Tanaka")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/siketyan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/12772118?u=1a51e0a06690e52982e7594bc7379481e65155a1&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Natsuki Ikeguchi")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/mmuellersoppart"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/16762461?u=bef7454cb73c164b2d18e077e5ba6b7891aae3d2&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Marlon Mueller-Soppart")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/gitmalong"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/18363591?v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"ul")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/manfredcml"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/27536502?u=b71636bdabbc698458b32e2ac05c5771ad41097e&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Manfred Lee")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/kallydev"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/36319157?u=5be882aa4dbe7eea97b1a80a6473857369146df6&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"KallyDev")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/dsgallups"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/44790295?u=d1c8d2a60930dfbe95497df7fecf52cf5d95dd5f&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Daniel Gallups")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Coolpany-SE"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/96304487?v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Coolpany-SE"))))),(0,n.kt)("h2",{id:"rustacean-sticker-pack-"},"Rustacean Sticker Pack \ud83e\udd80"),(0,n.kt)("p",null,"The Rustacean Sticker Pack is the perfect way to express your passion for Rust.\nOur stickers are made with a premium water-resistant vinyl with a unique matte finish.\nStick them on your laptop, notebook, or any gadget to show off your love for Rust!"),(0,n.kt)("p",null,"Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects."),(0,n.kt)("p",null,"Sticker Pack Contents:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG"),(0,n.kt)("li",{parentName:"ul"},"Mascot of SeaQL: Terres the Hermit Crab"),(0,n.kt)("li",{parentName:"ul"},"Mascot of Rust: Ferris the Crab"),(0,n.kt)("li",{parentName:"ul"},"The Rustacean word")),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/sticker-pack/"},"Support SeaQL and get a Sticker Pack!")),(0,n.kt)("a",{href:"https://www.sea-ql.org/sticker-pack/"},(0,n.kt)("img",{style:{borderRadius:"25px"},alt:"Rustacean Sticker Pack by SeaQL",src:"https://www.sea-ql.org/static/sticker-pack-1s.jpg"})))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/blog/assets/js/b2f554cd.93bd73b0.js b/blog/assets/js/b2f554cd.93bd73b0.js new file mode 100644 index 00000000000..676cc8243da --- /dev/null +++ b/blog/assets/js/b2f554cd.93bd73b0.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunksea_ql_blog=self.webpackChunksea_ql_blog||[]).push([[1477],{10:e=>{e.exports=JSON.parse('{"blogPosts":[{"id":"2024-01-23-whats-new-in-seaorm-0.12.x","metadata":{"permalink":"/blog/2024-01-23-whats-new-in-seaorm-0.12.x","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md","source":"@site/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md","title":"What\'s new in SeaORM 0.12.x","description":"It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!","date":"2024-01-23T00:00:00.000Z","formattedDate":"January 23, 2024","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":6.89,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/blog/img/SeaQL.png"}],"frontMatter":{"slug":"2024-01-23-whats-new-in-seaorm-0.12.x","title":"What\'s new in SeaORM 0.12.x","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/blog/img/SeaQL.png","tags":["news"]},"nextItem":{"title":"SeaQL Community Survey 2023 Results","permalink":"/blog/2024-01-18-community-survey-2023"}},"content":"\\"SeaORM\\n\\nIt had been a while since the initial [SeaORM 0.12 release](https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/). This blog post summarizes the new features and enhancements introduced in SeaORM [`0.12.2`](https://github.com/SeaQL/sea-orm/releases/tag/0.12.2) through [`0.12.12`](https://github.com/SeaQL/sea-orm/releases/tag/0.12.12)!\\n\\n## Celebrating 2M downloads on crates.io \ud83d\udce6\\n\\nWe\'ve just reached the milestone of 2,000,000 all time downloads on [crates.io](https://crates.io/crates/sea-orm). It\'s a testament to SeaORM\'s adoption in professional use. Thank you to all our users for your trust and for being a part of our community.\\n\\n## New Features\\n\\n### Entity format update\\n\\n* [#1898](https://github.com/SeaQL/sea-orm/pull/1898) Add support for root JSON arrays (requires the `json-array` / `postgres-array` feature)! It involved an intricate type system refactor to work around the orphan rule.\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"json_struct_vec\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n #[sea_orm(column_type = \\"Json\\")]\\n pub struct_vec: Vec,\\n}\\n\\n#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]\\npub struct JsonColumn {\\n pub value: String,\\n}\\n```\\n\\n* [#2009](https://github.com/SeaQL/sea-orm/pull/2009) Added `comment` attribute for Entity; `create_table_from_entity` now supports comment on MySQL\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"applog\\", comment = \\"app logs\\")]\\npub struct Model {\\n #[sea_orm(primary_key, comment = \\"ID\\")]\\n pub id: i32,\\n #[sea_orm(comment = \\"action\\")]\\n pub action: String,\\n pub json: Json,\\n pub created_at: DateTimeWithTimeZone,\\n}\\n```\\n\\n### Cursor paginator improvements\\n\\n* [#2037](https://github.com/SeaQL/sea-orm/pull/2037) Added descending order to Cursor:\\n\\n```rust\\n// (default behaviour) Before 5 ASC, i.e. id < 5\\n\\nlet mut cursor = Entity::find().cursor_by(Column::Id);\\ncursor.before(5);\\n\\nassert_eq!(\\n cursor.first(4).all(db).await?,\\n [\\n Model { id: 1 },\\n Model { id: 2 },\\n Model { id: 3 },\\n Model { id: 4 },\\n ]\\n);\\n\\n// (new API) After 5 DESC, i.e. id < 5\\n\\nlet mut cursor = Entity::find().cursor_by(Column::Id);\\ncursor.after(5).desc();\\n\\nassert_eq!(\\n cursor.first(4).all(db).await?,\\n [\\n Model { id: 4 },\\n Model { id: 3 },\\n Model { id: 2 },\\n Model { id: 1 },\\n ]\\n);\\n```\\n\\n* [#1826](https://github.com/SeaQL/sea-orm/pull/1826) Added cursor support to `SelectTwo`:\\n\\n```rust\\n// Join with linked relation; cursor by first table\'s id\\n\\ncake::Entity::find()\\n .find_also_linked(entity_linked::CakeToFillingVendor)\\n .cursor_by(cake::Column::Id)\\n .before(10)\\n .first(2)\\n .all(&db)\\n .await?\\n\\n// Join with relation; cursor by the 2nd table\'s id \\n\\ncake::Entity::find()\\n .find_also_related(Fruit)\\n .cursor_by_other(fruit::Column::Id)\\n .before(10)\\n .first(2)\\n .all(&db)\\n .await?\\n```\\n\\n### Added \\"proxy\\" to database backend\\n\\n[#1881](https://github.com/SeaQL/sea-orm/pull/1881), [#2000](https://github.com/SeaQL/sea-orm/pull/2000) Added \\"proxy\\" to database backend (requires feature flag `proxy`).\\n\\nIt enables the possibility of using SeaORM on edge / client-side! See the [GlueSQL demo](https://github.com/SeaQL/sea-orm/tree/master/examples/proxy_gluesql_example) for an example.\\n\\n## Enhancements\\n\\n* [#1954](https://github.com/SeaQL/sea-orm/pull/1954) [sea-orm-macro] Added `#[sea_orm(skip)]` to `FromQueryResult` derive macro\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]\\npub struct PublicUser {\\n pub id: i64,\\n pub name: String,\\n #[serde(skip_serializing_if = \\"Vec::is_empty\\")]\\n #[sea_orm(skip)]\\n pub something: Something,\\n}\\n```\\n\\n* [#1598](https://github.com/SeaQL/sea-orm/pull/1598) [sea-orm-macro] Added support for Postgres arrays in `FromQueryResult` impl of `JsonValue`\\n```rust\\n// existing API:\\n\\nassert_eq!(\\n Entity::find_by_id(1).one(db).await?,\\n Some(Model {\\n id: 1,\\n name: \\"Collection 1\\".into(),\\n integers: vec![1, 2, 3],\\n teas: vec![Tea::BreakfastTea],\\n colors: vec![Color::Black],\\n })\\n);\\n\\n// new API:\\n\\nassert_eq!(\\n Entity::find_by_id(1).into_json().one(db).await?,\\n Some(json!({\\n \\"id\\": 1,\\n \\"name\\": \\"Collection 1\\",\\n \\"integers\\": [1, 2, 3],\\n \\"teas\\": [\\"BreakfastTea\\"],\\n \\"colors\\": [0],\\n }))\\n);\\n```\\n\\n* [#1828](https://github.com/SeaQL/sea-orm/pull/1828) [sea-orm-migration] Check if an index exists\\n```rust\\nuse sea_orm_migration::prelude::*;\\n#[derive(DeriveMigrationName)]\\npub struct Migration;\\n#[async_trait::async_trait]\\nimpl MigrationTrait for Migration {\\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n // ...\\n\\n // Make sure the index haven\'t been created\\n assert!(!manager.has_index(\\"cake\\", \\"cake_name_index\\").await?);\\n\\n manager\\n .create_index(\\n Index::create()\\n .name(\\"cake_name_index\\")\\n .table(Cake::Table)\\n .col(Cake::Name)\\n .to_owned(),\\n )\\n .await?;\\n\\n Ok(())\\n }\\n\\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n // ...\\n }\\n}\\n```\\n\\n* [#2030](https://github.com/SeaQL/sea-orm/pull/2030) Improve query performance of `Paginator`\'s `COUNT` query\\n* [#2055](https://github.com/SeaQL/sea-orm/pull/2055) Added SQLx slow statements logging to `ConnectOptions`\\n* [#1867](https://github.com/SeaQL/sea-orm/pull/1867) Added `QuerySelect::lock_with_behavior`\\n* [#2002](https://github.com/SeaQL/sea-orm/pull/2002) Cast enums in `is_in` and `is_not_in`\\n* [#1999](https://github.com/SeaQL/sea-orm/pull/1999) Add source annotations to errors\\n* [#1960](https://github.com/SeaQL/sea-orm/issues/1960) Implement `StatementBuilder` for `sea_query::WithQuery`\\n* [#1979](https://github.com/SeaQL/sea-orm/pull/1979) Added method `expr_as_` that accepts `self`\\n* [#1868](https://github.com/SeaQL/sea-orm/pull/1868) Loader: use `ValueTuple` as hash key\\n* [#1934](https://github.com/SeaQL/sea-orm/pull/1934) [sea-orm-cli] Added `--enum-extra-derives`\\n* [#1952](https://github.com/SeaQL/sea-orm/pull/1952) [sea-orm-cli] Added `--enum-extra-attributes`\\n* [#1693](https://github.com/SeaQL/sea-orm/pull/1693) [sea-orm-cli] Support generation of related entity with composite foreign key\\n\\n## Bug fixes\\n\\n* [#1855](https://github.com/SeaQL/sea-orm/pull/1855), [#2054](https://github.com/SeaQL/sea-orm/pull/2054) [sea-orm-macro] Qualify types in `DeriveValueType` macro\\n* [#1953](https://github.com/SeaQL/sea-orm/pull/1953) [sea-orm-cli] Fix duplicated active enum use statements on generated entities\\n* [#1821](https://github.com/SeaQL/sea-orm/pull/1821) [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants\\n* [#2071](https://github.com/SeaQL/sea-orm/pull/2071) [sea-orm-cli] Fix entity generation for relations with composite keys\\n* [#1800](https://github.com/SeaQL/sea-orm/issues/1800) Fixed `find_with_related` consolidation logic\\n* [5a6acd67](https://github.com/SeaQL/sea-orm/commit/5a6acd67312601e4dba32896600044950e20f99f) Fixed `Loader` panic on empty inputs\\n\\n## Upgrades\\n\\n* [#1984](https://github.com/SeaQL/sea-orm/pull/1984) Upgraded `axum` example to `0.7`\\n* [#1858](https://github.com/SeaQL/sea-orm/pull/1858) Upgraded `chrono` to `0.4.30`\\n* [#1959](https://github.com/SeaQL/sea-orm/pull/1959) Upgraded `rocket` to `0.5.0`\\n* Upgraded `sea-query` to `0.30.5`\\n* Upgraded `sea-schema` to `0.14.2`\\n* Upgraded `salvo` to `0.50`\\n\\n## House Keeping\\n\\n* [#2057](https://github.com/SeaQL/sea-orm/pull/2057) Fix clippy warnings on 1.75\\n* [#1811](https://github.com/SeaQL/sea-orm/pull/1811) Added test cases for `find_xxx_related/linked`\\n\\n## Release planning\\n\\nIn the [announcement blog post](https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/) of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we\'ve accomplished that!\\n\\nThere are still a few breaking changes planned for the next major release. After some [discussions](https://github.com/SeaQL/sea-orm/discussions/2031) and consideration, we decided that the next major release will be a release candidate for 1.0!\\n\\n## Sponsor\\n\\nA big thank to [DigitalOcean](https://www.digitalocean.com/) who sponsored our servers, and [JetBrains](https://www.jetbrains.com/) who sponsored our IDE, and every sponsor on [GitHub Sponsor](https://github.com/sponsors/SeaQL)!\\n\\nIf you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n#### Gold Sponsors\\n\\n\\n \\n \\n\\n\\n#### Sponsors\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Afonso Barracha\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marcus Buffett\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ren\xe9 Kla\u010dan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n IceApinan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Jacob Trueb\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Kentaro Tanaka\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Natsuki Ikeguchi\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marlon Mueller-Soppart\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n ul\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Manfred Lee\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n KallyDev\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Daniel Gallups\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Coolpany-SE\\n
\\n
\\n
\\n
\\n
\\n\\n## Rustacean Sticker Pack \ud83e\udd80\\n\\nThe Rustacean Sticker Pack is the perfect way to express your passion for Rust.\\nOur stickers are made with a premium water-resistant vinyl with a unique matte finish.\\nStick them on your laptop, notebook, or any gadget to show off your love for Rust!\\n\\nMoreover, all proceeds contributes directly to the ongoing development of SeaQL projects.\\n\\nSticker Pack Contents:\\n- Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG\\n- Mascot of SeaQL: Terres the Hermit Crab\\n- Mascot of Rust: Ferris the Crab\\n- The Rustacean word\\n\\n[Support SeaQL and get a Sticker Pack!](https://www.sea-ql.org/sticker-pack/)\\n\\n\\"Rustacean"},{"id":"2024-01-18-community-survey-2023","metadata":{"permalink":"/blog/2024-01-18-community-survey-2023","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2024-01-18-community-survey-2023.mdx","source":"@site/blog/2024-01-18-community-survey-2023.mdx","title":"SeaQL Community Survey 2023 Results","description":"524 members of the SeaQL community from 41 countries kindly contributed their thoughts on using SeaQL libraries, learning Rust and employing Rust in their day to day development lives.","date":"2024-01-18T00:00:00.000Z","formattedDate":"January 18, 2024","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":9.08,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Billy Chan","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2024-01-18-community-survey-2023","title":"SeaQL Community Survey 2023 Results","author":"SeaQL Team","author_title":"Billy Chan","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"],"toc_max_heading_level":2},"prevItem":{"title":"What\'s new in SeaORM 0.12.x","permalink":"/blog/2024-01-23-whats-new-in-seaorm-0.12.x"},"nextItem":{"title":"OpenUK Award 2023 \ud83c\udfc6","permalink":"/blog/2023-11-25-openuk-award"}},"content":"import * as Chart from \'../src/chart.js\';\\n\\n524 members of the SeaQL community from 41 countries kindly contributed their thoughts on using SeaQL libraries, learning Rust and employing Rust in their day to day development lives.\\nFrom these responses we hope to get an understanding of where the SeaQL and Rust community stands in 2023.\\n\\nThis is our first community survey, we will conduct the survey annually to keep track of how the community evolves over time.\\n\\n## Demographics\\n\\n### Q. Where are you located in?\\n\\nParticipants are from 41 countries across the world!\\n\\n\\n Other: \\n\\n\\n## Use of SeaQL Libraries\\n\\n### Q. Are you using SeaQL libraries in building a project?\\n\\n\\n\\n### Q. Which SeaQL libraries are you using in building a project?\\n\\n\\n Other: \\n\\n\\n### Q. Are you using SeaQL libraries in a personal, academic or professional context?\\n\\n\\n\\n### Q. Why did you choose SeaQL libraries?\\n\\n\\n Other: \\n\\n\\n### Q. What qualities of SeaQL libraries do you think are important?\\n\\n\\n Other: \\n\\n\\n## Team & Project Nature\\n\\n### Q. How many team members (including you) are working on the project?\\n\\n\\n\\n### Q. Can you categorize the nature of the project?\\n\\n\\n Other: \\n\\n\\n## Tech Stack\\n\\n### Q. What is your development environment?\\n\\n\\n\\n### Linux Breakdown\\n\\n\\n\\n### Windows Breakdown\\n\\n\\n\\n### macOS Breakdown\\n\\n\\n\\n### Q. Which database(s) do you use?\\n\\n\\n\\n### Q. Which web framework are you using?\\n\\n\\n\\n### Q. What is the deployment environment?\\n\\n\\n\\n## Rust at Work\\n\\n### Q. Are you using Rust at work?\\n\\n\\n\\n### Q. Which industry your company is in?\\n\\n\\n\\n#### Vague description of the company\\n\\n\\n\\n### Q. What is the size of your company?\\n\\n\\n\\n### Q. How many engineers in your company are dedicated to writing Rust?\\n\\n\\n\\n### Q. Which layer(s) of the technology stack are using Rust?\\n\\n\\n\\n## Learning Rust\\n\\n### Q. Are you learning / new to Rust?\\n\\n\\n\\n### Q. Which language(s) are you most familiar with?\\n\\n\\n\\n### Q. Are you familiar with SQL?\\n\\n\\n\\n### Q. Do you find Rust easy or hard to learn?\\n\\n\\n\\n### Q. What motivates you to learn Rust?\\n\\n\\n Other: \\n\\n\\n### Q. What learning resources do you rely on?\\n\\n\\n Other: \\n\\n\\n### Q. What is your first project built using Rust?\\n\\n\\n Other: \\n\\n\\n## What\'s Next\\n\\n### Q. Which aspects do you want to see advancement on SeaORM?\\n\\nThank you for all the suggestions, we will certainly take them into account!\\n\\n\\n Other: \\n\\n\\n### Q. What tools would you be interested in using, if developed first-party by SeaQL?\\n\\n\\n Other: \\n\\n\\n## Share Your Thoughts\\n\\n### Q. Anything else you want to say?\\n\\nDidn\'t expect this section to turn into a testimonial, thank you for all the kind words :)\\n\\n> Good job yall\\n\\n> Great projects, thanks for your hard work\\n\\n> I expect it to be an asynchronous type-safe library. Keep up the good work!\\n\\n> I\'d like to see entity generation without a database\\n\\n> The website, support from JetBrains, the documentation and the release cycle are very nice!\\n\\n> I\'m very interested in how SeaORM will continue evolving and I would like to wish you the best of luck!\\n\\n> I\'ve found SeaORM very useful and I\'m very grateful to the development team for creating and maintaining it!\\n\\n> In TypeORM I can write entities and then generate migration from them. It\'s very handy. It helps to increase development speed. It would be nice to have this functionality in SeaORM.\\n\\n> It needs to have better integration with SeaQuery, I sometimes need to get to it because not all features are available in SeaORM which makes it a pain.\\n\\n> Keep the good work!\\n\\n> Keep going! Love SeaORM!\\n\\n> Keep up the great work. Rust needs a fast, ergonomic and reliable ORM.\\n\\n> SeaORM is very powerful, but the rust docs and tutorial examples could be more fleshed out.\\n\\n> SeaORM is an awesome library. Most things are quite concise and therefore straightforward. Simply a few edge cases concerning DB specific types and values could be better.\\n\\n> The trait system is too complex and coding experience is not pretty well with that.\\n\\n> Automatic migration generation would make the library pretty much perfect in my opinion.\\n\\n> SeaQL tutorials could be better. Much more detailed explanation and definitely it has to have best practices section for Design Patterns like and good best practices related with clean architecture.\\n\\n> SeaQL are great products and it\u2019s very enjoyable using them\\n\\n> Thank you <3\\n\\n> Thank you for awesome library!\\n\\n> Thank you for this wonderful project. I feel the documentation lacks examples for small functions and usage of some obscure features.\\n\\n> Thank you for your hard work!\\n\\n> Thank you for your work on SeaQL, your efforts are appreciated \\n\\n> Thank you for your work, we are seeking actively to include SeaORM in our projects\\n\\n> Thank you very much for your work!\\n\\n> Thanks a lot for the amazing work you guys put into this set of libraries. This is an amazing development for the rust ecosystem.\\n\\n> Thanks and keep up the good work.\\n\\n> Thanks for a great tool!\\n\\n> Thanks for all the amazing work.\\n\\n> Thanks for making SeaORM!\\n\\n> The project I am doing for work is only a prototype, it\'s a new implementation of a current Python forecasting project which uses a pandas and a custom psycopg2 orm. My intent is to create a faster/dev friendly version with SeaORM and Polars. I am hoping to eventually get a prototype I can display to my team to get a go ahead to fully develop a new version, and to migrate 4-5 other forecasting apps using shared libraries for io and calculations.\\n\\n> I have also been using SeaORM for a small API client for financial data, which I may make open source.\\n\\n> I think one thing which could really improve SeaORM is some more advance examples in the documentation section. The docs are really detailed as far as rust documentation goes.\\n\\n> Very promising project, keep it up.\\n\\n> Thank you so much for taking it upon yourselves to selflessly give your free time. It probably doesn\'t matter much, but thank you so much for your work. SeaORM is a fantastic tool that I can see myself using for a long time to come. I hope to make contributions in any form when I am under better circumstances :3 Kudos to the team!\\n\\n> \u4f60\u4eec\u7684\u5e93\u975e\u5e38\u7684\u68d2\uff0c\u81f3\u5c11\u6211\u89c9\u5f97\u6bd4Diesel\u597d\u592a\u591a\u4e86\uff0c\u5165\u95e8\u7b80\u5355\uff0c\u5bf9\u65b0\u624b\u975e\u5e38\u53cb\u597d\uff0c\u8fd9\u662f\u6700\u5927\u7684\u4eae\u70b9\uff0c\u5176\u6b21\u662f\u5e93\u8c8c\u4f3c\u53ef\u4ee5\u5b9e\u73b0\u5f88\u590d\u6742\u7684Join SQL\u903b\u8f91\u800c\u4e0d\u7528\u5199\u539f\u751f\u7684SQL\uff0c\u8fd9\u70b9\u4e5f\u662f\u975e\u5e38\u503c\u5f97\u70b9\u8d5e\u7684\uff0c\u4f46\u662f\u5728\u8fd9\u5757\u7684\u6587\u6863\u8c8c\u4f3c\u5199\u7684\u6709\u70b9\u7b80\u7565\u4e86\uff0c\u5e0c\u671b\u53ef\u4ee5\u4e30\u5bcc\u4e00\u4e0b\u6587\u6863\u5185\u5bb9\uff0c\u5bf9\u4e8e\u590d\u6742\u67e5\u8be2\u7684\u8bf4\u660e\u53ef\u4ee5\u66f4\u52a0\u8be6\u7ec6\u4e00\u4e9b\uff0c\u8fd9\u6837\u5c31\u518d\u597d\u4e0d\u8fc7\u4e86\u3002\u8c22\u8c22\u4f60\u4eec\uff0c\u6211\u4f1a\u6301\u7eed\u5173\u6ce8\u4f60\u4eec\uff0c\u672a\u6765\u7684\u9879\u76ee\u5982\u679c\u6d89\u53caORM\uff0c\u90a3\u7edd\u5bf9\u975e\u4f60\u4eec\u83ab\u5c5e\u4e86\uff01\\n\\n## Rustacean Sticker Pack \ud83e\udd80\\n\\nThe Rustacean Sticker Pack is the perfect way to express your passion for Rust.\\nOur stickers are made with a premium water-resistant vinyl with a unique matte finish.\\nStick them on your laptop, notebook, or any gadget to show off your love for Rust!\\n\\nMoreover, all proceeds contributes directly to the ongoing development of SeaQL projects.\\n\\nSticker Pack Contents:\\n- Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG\\n- Mascot of SeaQL: Terres the Hermit Crab\\n- Mascot of Rust: Ferris the Crab\\n- The Rustacean word\\n\\n[Support SeaQL and get a Sticker Pack!](https://www.sea-ql.org/sticker-pack/)\\n\\n\\"Rustacean"},{"id":"2023-11-25-openuk-award","metadata":{"permalink":"/blog/2023-11-25-openuk-award","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-11-25-openuk-award.md","source":"@site/blog/2023-11-25-openuk-award.md","title":"OpenUK Award 2023 \ud83c\udfc6","description":"It is our honour to have been awarded by OpenUK for the 2023 Award in the Software category! The award ceremony was a very memorable experience. A huge thanks to Red Badger who sponsored the software award.","date":"2023-11-25T00:00:00.000Z","formattedDate":"November 25, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":0.805,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2023-11-25-openuk-award","title":"OpenUK Award 2023 \ud83c\udfc6","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"SeaQL Community Survey 2023 Results","permalink":"/blog/2024-01-18-community-survey-2023"},"nextItem":{"title":"Writing Async Runtime Generic Library","permalink":"/blog/2023-11-22-async-runtime-generic"}},"content":"\\n\\nIt is our honour to have been awarded by [OpenUK for the 2023 Award](https://openuk.uk/awards/) in the Software category! The award ceremony was a very memorable experience. A huge thanks to [Red Badger](https://red-badger.com/) who sponsored the software award.\\n\\nIn 2023, we released SeaStreamer, two major versions of SeaORM, a new version of Seaography, and have been busy working on a new project on the side.\\n\\nWe reached the milestone of 5k GitHub stars and 2M crates.io downloads mid-year.\\n\\nIn the summer, we took in two interns for our 3rd summer of code.\\n\\nWe plan to offer internships tailored to UK students in 2024 through university internship programs. As always, we welcome contributors from all over the world, and may be we will enrol on GSoC 2024 again. (but open-source is not bounded any schedule, so you can start contributing anytime)\\n\\nA big thanks to our sponsors who continued to support us, and we look forward to a more impactful 2024."},{"id":"2023-11-22-async-runtime-generic","metadata":{"permalink":"/blog/2023-11-22-async-runtime-generic","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-11-22-async-runtime-generic.md","source":"@site/blog/2023-11-22-async-runtime-generic.md","title":"Writing Async Runtime Generic Library","description":"If you are writing an async application in Rust, at some point you\'d want to separate the code into several crates. There are some benefits:","date":"2023-11-22T00:00:00.000Z","formattedDate":"November 22, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.49,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2023-11-22-async-runtime-generic","title":"Writing Async Runtime Generic Library","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"OpenUK Award 2023 \ud83c\udfc6","permalink":"/blog/2023-11-25-openuk-award"},"nextItem":{"title":"What\'s new in SeaStreamer 0.3","permalink":"/blog/2023-09-06-whats-new-in-sea-streamer-0.3"}},"content":"If you are writing an async application in Rust, at some point you\'d want to separate the code into several crates. There are some benefits:\\n\\n1. Better encapsulation. Having a crate boundary between sub-systems can lead to cleaner code and a more well-defined API. No more `pub(crate)`!\\n2. Faster compilation. By breaking down a big crate into several independent small crates, they can be compiled concurrently.\\n\\nBut the question is, if you are using only one async runtime anyway, what are the benefits of writing async-runtime-generic libraries?\\n\\n1. Portability. You can easily switch to a different async runtime, or wasm.\\n2. Correctness. Testing a library against both `tokio` and `async-std` can uncover more bugs, including concurrency bugs (due to fuzzy task execution orders) and \\"undefined behaviour\\" either due to misunderstanding or async-runtime implementation details\\n\\nSo now you\'ve decided to write async-runtime-generic libraries! Here I want to share 3 strategies along with examples found in the Rust ecosystem.\\n\\n### Approach 1: Defining your own `AsyncRuntime` trait\\n\\nUsing the [`futures`](https://docs.rs/futures/latest/futures/) crate you can write very generic library code, but there is one missing piece: `time` - to `sleep` or `timeout`, you have to rely on an async runtime. If that\'s all you need, you can define your own `AsyncRuntime` trait and requires downstream to implement it. This is the approach used by [rdkafka](https://docs.rs/rdkafka/latest/rdkafka/):\\n\\n```rust\\npub trait AsyncRuntime: Send + Sync + \'static {\\n type Delay: Future + Send;\\n\\n /// It basically means the return value must be a `Future`\\n fn sleep(duration: Duration) -> Self::Delay;\\n}\\n```\\n\\nHere is how it\'s implemented:\\n\\n```rust\\nimpl AsyncRuntime for TokioRuntime {\\n type Delay = tokio::time::Sleep;\\n\\n fn sleep(duration: Duration) -> Self::Delay {\\n tokio::time::sleep(duration)\\n }\\n}\\n```\\n\\nLibrary code to use the above:\\n\\n```rust\\nasync fn operation() {\\n R::sleep(Duration::from_millis(1)).await;\\n}\\n```\\n\\n### Approach 2: Abstract the async runtimes internally and expose feature flags\\n\\nThis is the approach used by [redis-rs](https://docs.rs/redis/latest/redis/).\\n\\nTo work with network connections or file handle, you can use the `AsyncRead` / `AsyncWrite` traits:\\n\\n```rust\\n#[async_trait]\\npub(crate) trait AsyncRuntime: Send + Sync + \'static {\\n type Connection: AsyncRead + AsyncWrite + Send + Sync + \'static;\\n\\n async fn connect(addr: SocketAddr) -> std::io::Result;\\n}\\n```\\n\\nThen you\'ll define a module for each async runtime:\\n\\n```rust\\n#[cfg(feature = \\"runtime-async-std\\")]\\nmod async_std_impl;\\n#[cfg(feature = \\"runtime-async-std\\")]\\nuse async_std_impl::*;\\n\\n#[cfg(feature = \\"runtime-tokio\\")]\\nmod tokio_impl;\\n#[cfg(feature = \\"runtime-tokio\\")]\\nuse tokio_impl::*;\\n```\\n\\nWhere each module would look like:\\n\\n```rust title=\\"tokio_impl.rs\\"\\n#[async_trait]\\nimpl AsyncRuntime for TokioRuntime {\\n type Connection = tokio::net::TcpStream;\\n\\n async fn connect(addr: SocketAddr) -> std::io::Result {\\n tokio::net::TcpStream::connect(addr).await\\n }\\n}\\n```\\n\\nLibrary code to use the above:\\n\\n```rust\\nasync fn operation(conn: R::Connection) {\\n conn.write(b\\"some bytes\\").await;\\n}\\n```\\n\\n### Approach 3: Maintain an async runtime abstraction crate\\n\\nThis is the approach used by [SQLx](https://docs.rs/crate/sqlx-rt) and [SeaStreamer](https://docs.rs/sea-streamer-runtime/latest/sea_streamer_runtime/).\\n\\nBasically, aggregate all async runtime APIs you\'d use and write a wrapper library. This may be tedious, but this also has the benefit of specifying *all interactions* with the async runtime *in one place* for *your* project, which could be handy for debugging or tracing.\\n\\nFor example, async `Task` handling:\\n\\n```rust title=\\"common-async-runtime/tokio_task.rs\\"\\npub use tokio::task::{JoinHandle as TaskHandle};\\n\\npub fn spawn_task(future: F) -> TaskHandle\\nwhere\\n F: Future + Send + \'static,\\n T: Send + \'static,\\n{\\n tokio::task::spawn(future)\\n}\\n```\\n\\n`async-std`\'s task API is slightly different (in `tokio` the output is `Result`), which requires some boilerplate:\\n\\n```rust title=\\"common-async-runtime/async_std_task.rs\\"\\n/// A shim to match tokio\'s API\\npub struct TaskHandle(async_std::task::JoinHandle);\\n\\npub fn spawn_task(future: F) -> TaskHandle\\nwhere\\n F: Future + Send + \'static,\\n T: Send + \'static,\\n{\\n TaskHandle(async_std::task::spawn(future))\\n}\\n\\n#[derive(Debug)]\\npub struct JoinError;\\n\\nimpl std::error::Error for JoinError {}\\n\\n// This is basically how you wrap a `Future`\\nimpl Future for TaskHandle {\\n type Output = Result;\\n\\n fn poll(\\n mut self: std::pin::Pin<&mut Self>,\\n cx: &mut std::task::Context<\'_>,\\n ) -> std::task::Poll {\\n match self.0.poll_unpin(cx) {\\n std::task::Poll::Ready(res) => std::task::Poll::Ready(Ok(res)),\\n std::task::Poll::Pending => std::task::Poll::Pending,\\n }\\n }\\n}\\n```\\n\\nIn the library\'s `Cargo.toml`, you can simply include `common-async-runtime` as dependency. This makes your library code \'pure\', because now selecting an async runtime is controlled by downstream. Similar to approach 1, this crate can be compiled *without any* async runtime, which is neat!\\n\\n### Conclusion\\n\\nHappy hacking! Welcome to share your experience with the [community](https://github.com/SeaQL/sea-streamer/discussions)."},{"id":"2023-09-06-whats-new-in-sea-streamer-0.3","metadata":{"permalink":"/blog/2023-09-06-whats-new-in-sea-streamer-0.3","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-09-06-whats-new-in-sea-streamer-0.3.md","source":"@site/blog/2023-09-06-whats-new-in-sea-streamer-0.3.md","title":"What\'s new in SeaStreamer 0.3","description":"\ud83c\udf89 We are pleased to release SeaStreamer 0.3.x!","date":"2023-09-06T00:00:00.000Z","formattedDate":"September 6, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.265,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2023-09-06-whats-new-in-sea-streamer-0.3","title":"What\'s new in SeaStreamer 0.3","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Writing Async Runtime Generic Library","permalink":"/blog/2023-11-22-async-runtime-generic"},"nextItem":{"title":"Announcing SeaORM 0.12 \ud83d\udc1a","permalink":"/blog/2023-08-12-announcing-seaorm-0.12"}},"content":"\\n\\n\ud83c\udf89 We are pleased to release SeaStreamer [`0.3.x`](https://github.com/SeaQL/sea-streamer/releases/0.3.0)!\\n\\n## File Backend\\n\\nA major addition in SeaStreamer `0.3` is the file backend. It implements the same high-level MPMC API, enabling streaming to and from files. There are different use cases. For example, it can be used to dump data from Redis / Kafka and process them locally, or as an intermediate file format for storage or transport.\\n\\nThe SeaStreamer File format, `.ss` is pretty simple. It\'s very much like `.ndjson`, but binary. The file format is designed with the following goals:\\n\\n1. Binary data support without encoding overheads\\n2. Efficiency in rewinding / seeking through a large dump\\n3. Streaming-friendliness - File can be truncated without losing integrity\\n\\nLet me explain in details.\\n\\nFirst of all, SeaStreamer File is a container format. It only concerns the message stream and framing, not the payload. It\'s designed to be paired with a binary message format like Protobuf or BSON.\\n\\n### Encode-free\\n\\nJSON and CSV are great plain text file formats, but they are not binary friendly. Usually, to encode binary data, one would use `base64`. It therefore imposes an expensive encoding / decoding overhead. In a binary protocol, *delimiters* are frequently used to signal message boundaries. As a consequence, byte stuffing is needed to escape the bytes.\\n\\nIn SeaStreamer, we want to avoid the encoding overhead entirely. The payload should be written to disk verbatim. So the file format revolves around constructing message frames and placing checksums to ensure that data is interpreted correctly.\\n\\n### Efficient seek\\n\\nA delimiter-based protocol has an advantage: the byte stream can be randomly sought, and we always have no trouble reading the next message.\\n\\nSince SeaStreamer does not rely on delimiters, we can\'t easily align to message frames after a random seek. We solve this problem by placing beacons in a regular interval at fixed locations throughout the file. E.g. say the `beacon interval` is `1024`, there will be a beacon at the 1024th byte, the 2048th, and so on. Then, every time we want to seek to a random location, we\'d seek to the closest N * 1024 byte and read from there.\\n\\nThese beacons also double as indices: they contain summaries of the individual streams. So given a particular stream key and sequence number (or timestamp) to search for, SeaStreamer can quickly locate the message *just by* reading the beacons. It doesn\'t matter if the stream\'s messages are sparse!\\n\\n### Streaming-friendliness\\n\\nIt should always be safe to truncate files. It should be relatively easy to split a file into chunks. We should be able to tell if the data is corrupted.\\n\\nSeaStreamer achieves this by computing a checksum for every message, and also the running checksum of the checksums for each stream. It\'s not enforced right now, but in theory we can detect if any messages are missing from a stream.\\n\\n#### Summary\\n\\nThis file format is also easy to implement in different languages, as we just made an (experimental) [reader in Typescript](https://github.com/SeaQL/sea-streamer/tree/main/sea-streamer-file/sea-streamer-file-reader).\\n\\nThat\'s it! If you are interested, you can go and take a look at the [format description](https://docs.rs/sea-streamer-file/latest/sea_streamer_file/format/index.html).\\n\\n## Redis Backend\\n\\nRedis Streams are underrated! They have high throughput and concurrency, and are best suited for non-persistent stream processing near or on the same host as the application.\\n\\nThe obstacle is probably in library support. Redis Streams\' API is rather low level, and there aren\'t many high-level libraries to help with programming, as opposed to Kafka, which has versatile official programming libraries.\\n\\nThe pitfall is, it\'s not easy to maximize concurrency with the raw Redis API. To start, you\'d need to pipeline `XADD` commands. You\'d also need to time and batch `XACK`s so that it does not block reads and computation. And of course you want to separate the reads and writes on different threads.\\n\\nSeaStreamer breaks these obstacles for you and offers a Kafka-like API experience!\\n\\n## Benchmark\\n\\nIn `0.3`, we have done some optimizations to improve the throughput of the Redis and File backend. We set our initial benchmark at 100k messages per second, which hopefully we can further improve over time.\\n\\nOur [micro benchmark](https://github.com/SeaQL/sea-streamer/tree/main/benchmark) involves a simple program producing or consuming 100k messages, where each message has a payload of 256 bytes.\\n\\nFor Redis, it\'s running on the same computer in Docker. On my not-very-impressive laptop with a 10th Gen Intel Core i7, the numbers are somewhat around:\\n\\n#### Producer\\n\\n```\\nredis 0.5s\\nstdio 0.5s\\nfile 0.5s\\n```\\n\\n#### Consumer\\n\\n```\\nredis 1.0s\\nstdio 1.0s\\nfile 1.1s\\n```\\n\\nIt practically means that we are comfortably in the realm of *producing* 100k messages per second, but are just about able to *consume* 100k messages in 1 second. Suggestions to performance improvements are welcome!\\n\\n## Community\\n\\nSeaQL.org is an independent open-source organization run by passionate \ufe0fdevelopers. If you like our projects, please star \u2b50 and share our repositories. If you feel generous, a small donation via [GitHub Sponsor](https://github.com/sponsors/SeaQL) will be greatly appreciated, and goes a long way towards sustaining the organization \ud83d\udea2.\\n\\nSeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future \ud83e\udd80."},{"id":"2023-08-12-announcing-seaorm-0.12","metadata":{"permalink":"/blog/2023-08-12-announcing-seaorm-0.12","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-08-12-announcing-seaorm-0.12.md","source":"@site/blog/2023-08-12-announcing-seaorm-0.12.md","title":"Announcing SeaORM 0.12 \ud83d\udc1a","description":"\ud83c\udf89 We are pleased to announce SeaORM 0.12 today!","date":"2023-08-12T00:00:00.000Z","formattedDate":"August 12, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":7.715,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/blog/img/SeaQL.png"}],"frontMatter":{"slug":"2023-08-12-announcing-seaorm-0.12","title":"Announcing SeaORM 0.12 \ud83d\udc1a","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/blog/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaStreamer 0.3","permalink":"/blog/2023-09-06-whats-new-in-sea-streamer-0.3"},"nextItem":{"title":"Introducing SeaStreamer \ud83c\udf0a","permalink":"/blog/2023-04-03-intro-sea-streamer"}},"content":"\\"SeaORM\\n\\n\ud83c\udf89 We are pleased to announce SeaORM [`0.12`](https://github.com/SeaQL/sea-orm/releases/tag/0.12.1) today!\\n\\nWe still remember the time when we first [introduced SeaORM](https://www.sea-ql.org/blog/2021-09-20-introducing-sea-orm/) to the Rust community two years ago. We set out a goal to enable developers to build asynchronous database-driven applications in Rust.\\n\\nToday, many open-source projects, [a handful of startups](https://www.sea-ql.org/SeaORM/index.html#our-users) and many more closed-source projects are using SeaORM. Thank you all who participated and contributed in the making!\\n\\n\\"SeaORM\\n\\n## New Features \ud83c\udf1f\\n\\n### \ud83e\udded [Seaography](https://github.com/SeaQL/seaography): GraphQL integration (preview)\\n\\n\\"Seaography\\n\\nSeaography is a GraphQL framework built on top of SeaORM. In `0.12`, Seaography integration is built into `sea-orm`.\\nSeaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a GraphQL server from SeaORM entities!\\n\\nWhile Seaography development is still in an early stage, it is especially useful in prototyping and building internal-use admin panels.\\n\\n[Read the documentation](https://www.sea-ql.org/SeaORM/docs/seaography/seaography-intro/) to learn more.\\n\\n### Added macro [`DerivePartialModel`](https://docs.rs/sea-orm/0.12.2/sea_orm/derive.DerivePartialModel.html)\\n\\n[#1597](https://github.com/SeaQL/sea-orm/pull/1597) Now you can easily perform custom select to query only the columns you needed\\n\\n```rust\\n#[derive(DerivePartialModel, FromQueryResult)]\\n#[sea_orm(entity = \\"Cake\\")]\\nstruct PartialCake {\\n name: String,\\n #[sea_orm(\\n from_expr = r#\\"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))\\"#\\n )]\\n name_upper: String,\\n}\\n\\nassert_eq!(\\n cake::Entity::find()\\n .into_partial_model::()\\n .into_statement(DbBackend::Sqlite)\\n .to_string(),\\n r#\\"SELECT \\"cake\\".\\"name\\", UPPER(\\"cake\\".\\"name\\") AS \\"name_upper\\" FROM \\"cake\\"\\"#\\n);\\n```\\n\\n### Added [`Select::find_with_linked`](https://docs.rs/sea-orm/0.12.2/sea_orm/query/struct.Select.html#method.find_with_linked)\\n\\n[#1728](https://github.com/SeaQL/sea-orm/pull/1728), [#1743](https://github.com/SeaQL/sea-orm/pull/1743) Similar to `find_with_related`, you can now select related entities and consolidate the models.\\n\\n```rust\\n// Consider the following link\\npub struct BakedForCustomer;\\n\\nimpl Linked for BakedForCustomer {\\n type FromEntity = Entity;\\n\\n type ToEntity = super::customer::Entity;\\n\\n fn link(&self) -> Vec {\\n vec![\\n super::cakes_bakers::Relation::Baker.def().rev(),\\n super::cakes_bakers::Relation::Cake.def(),\\n super::lineitem::Relation::Cake.def().rev(),\\n super::lineitem::Relation::Order.def(),\\n super::order::Relation::Customer.def(),\\n ]\\n }\\n}\\n\\nlet res: Vec<(baker::Model, Vec)> = Baker::find()\\n .find_with_linked(baker::BakedForCustomer)\\n .order_by_asc(baker::Column::Id)\\n .all(db)\\n .await?\\n```\\n\\n### Added [`DeriveValueType`](https://docs.rs/sea-orm/latest/sea_orm/derive.DeriveValueType.html) derive macro for [custom wrapper types](https://www.sea-ql.org/SeaORM/docs/generate-entity/newtype/)\\n\\n[#1720](https://github.com/SeaQL/sea-orm/pull/1720) So now you can use newtypes easily.\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"custom_value_type\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub number: Integer,\\n // Postgres only\\n pub str_vec: StringVec,\\n}\\n\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]\\npub struct Integer(i32);\\n\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]\\npub struct StringVec(pub Vec);\\n```\\n\\nWhich saves you the boilerplate of:\\n\\n```rust\\nimpl std::convert::From for Value { .. }\\n\\nimpl TryGetable for StringVec {\\n fn try_get_by(res: &QueryResult, idx: I)\\n -> Result { .. }\\n}\\n\\nimpl ValueType for StringVec {\\n fn try_from(v: Value) -> Result { .. }\\n\\n fn type_name() -> String { \\"StringVec\\".to_owned() }\\n\\n fn array_type() -> ArrayType { ArrayType::String }\\n\\n fn column_type() -> ColumnType { ColumnType::String(None) }\\n}\\n```\\n\\n## Enhancements \ud83c\udd99\\n\\n#### [#1433](https://github.com/SeaQL/sea-orm/pull/1433) Chained AND / OR join ON condition\\n\\nAdded more macro attributes to [`DeriveRelation`](https://docs.rs/sea-orm/0.12.2/sea_orm/derive.DeriveRelation.html)\\n\\n```rust\\n// Entity file\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\\npub enum Relation {\\n // By default, it\'s `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE \'%tropical%\'`\\n #[sea_orm(\\n has_many = \\"super::fruit::Entity\\",\\n on_condition = r#\\"super::fruit::Column::Name.like(\\"%tropical%\\")\\"#\\n )]\\n TropicalFruit,\\n // Specify `condition_type = \\"any\\"` to override it, now it becomes\\n // `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE \'%tropical%\'`\\n #[sea_orm(\\n has_many = \\"super::fruit::Entity\\",\\n on_condition = r#\\"super::fruit::Column::Name.like(\\"%tropical%\\")\\"#\\n condition_type = \\"any\\",\\n )]\\n OrTropicalFruit,\\n}\\n```\\n\\n#### [#1508](https://github.com/SeaQL/sea-orm/pull/1508) Supports entity with composite primary key of arity 12\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"primary_key_of_12\\")]\\npub struct Model {\\n #[sea_orm(primary_key, auto_increment = false)]\\n pub id_1: String,\\n ...\\n #[sea_orm(primary_key, auto_increment = false)]\\n pub id_12: bool,\\n}\\n```\\n\\n#### [#1677](https://github.com/SeaQL/sea-orm/pull/1677) Added [`UpdateMany::exec_with_returning()`](https://docs.rs/sea-orm/0.12.2/sea_orm/query/struct.UpdateMany.html#method.exec_with_returning)\\n\\n```rust\\nlet models: Vec = Entity::update_many()\\n .col_expr(Column::Values, Expr::expr(..))\\n .exec_with_returning(db)\\n .await?;\\n```\\n\\n#### [#1511](https://github.com/SeaQL/sea-orm/pull/1511) Added [`MigratorTrait::migration_table_name()`](https://docs.rs/sea-orm-migration/0.12.2/sea_orm_migration/migrator/trait.MigratorTrait.html#method.migration_table_name) method to configure the name of migration table\\n```rust\\n#[async_trait::async_trait]\\nimpl MigratorTrait for Migrator {\\n // Override the name of migration table\\n fn migration_table_name() -> sea_orm::DynIden {\\n Alias::new(\\"override_migration_table_name\\").into_iden()\\n }\\n ...\\n}\\n```\\n#### [#1707](https://github.com/SeaQL/sea-orm/pull/1707) Added [`DbErr::sql_err()`](https://docs.rs/sea-orm/0.12.2/sea_orm/error/enum.DbErr.html#method.sql_err) method to parse common database errors\\n```rust\\nassert!(matches!(\\n cake.into_active_model().insert(db).await\\n .expect_err(\\"Insert a row with duplicated primary key\\")\\n .sql_err(),\\n Some(SqlErr::UniqueConstraintViolation(_))\\n));\\n\\nassert!(matches!(\\n fk_cake.insert(db).await\\n .expect_err(\\"Insert a row with invalid foreign key\\")\\n .sql_err(),\\n Some(SqlErr::ForeignKeyConstraintViolation(_))\\n));\\n```\\n#### [#1737](https://github.com/SeaQL/sea-orm/pull/1737) Introduced new [`ConnAcquireErr`](https://docs.rs/sea-orm/0.12.2/sea_orm/error/enum.ConnAcquireErr.html)\\n```rust\\nenum DbErr {\\n ConnectionAcquire(ConnAcquireErr),\\n ..\\n}\\n\\nenum ConnAcquireErr {\\n Timeout,\\n ConnectionClosed,\\n}\\n```\\n#### [#1627](https://github.com/SeaQL/sea-orm/pull/1627) Added [`DatabaseConnection::ping()`](https://docs.rs/sea-orm/0.12.2/sea_orm/error/enum.ConnAcquireErr.html)\\n```rust\\n|db: DatabaseConnection| {\\n assert!(db.ping().await.is_ok());\\n db.clone().close().await;\\n assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire)));\\n}\\n```\\n#### [#1708](https://github.com/SeaQL/sea-orm/pull/1708) Added [`TryInsert`](https://docs.rs/sea-orm/0.12.2/sea_orm/query/struct.TryInsert.html) that does not panic on empty inserts\\n```rust\\n// now, you can do:\\nlet res = Bakery::insert_many(std::iter::empty())\\n .on_empty_do_nothing()\\n .exec(db)\\n .await;\\n\\nassert!(matches!(res, Ok(TryInsertResult::Empty)));\\n```\\n#### [#1712](https://github.com/SeaQL/sea-orm/pull/1712) Insert on conflict do nothing to return Ok\\n```rust\\nlet on = OnConflict::column(Column::Id).do_nothing().to_owned();\\n\\n// Existing behaviour\\nlet res = Entity::insert_many([..]).on_conflict(on).exec(db).await;\\nassert!(matches!(res, Err(DbErr::RecordNotInserted)));\\n\\n// New API; now you can:\\nlet res =\\nEntity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await;\\nassert!(matches!(res, Ok(TryInsertResult::Conflicted)));\\n```\\n#### [#1740](https://github.com/SeaQL/sea-orm/pull/1740), [#1755](https://github.com/SeaQL/sea-orm/pull/1755) Replacing `sea_query::Iden` with [`sea_orm::DeriveIden`](https://docs.rs/sea-orm/0.12.2/sea_orm/derive.DeriveIden.html)\\n\\nTo provide a more consistent interface, `sea-query/derive` is no longer enabled by `sea-orm`, as such, `Iden` no longer works as a derive macro (it\'s still a trait).\\n\\n```rust\\n// then:\\n\\n#[derive(Iden)]\\n#[iden = \\"category\\"]\\npub struct CategoryEnum;\\n\\n#[derive(Iden)]\\npub enum Tea {\\n Table,\\n #[iden = \\"AfternoonTea\\"]\\n EverydayTea,\\n}\\n\\n// now:\\n\\n#[derive(DeriveIden)]\\n#[sea_orm(iden = \\"category\\")]\\npub struct CategoryEnum;\\n\\n#[derive(DeriveIden)]\\npub enum Tea {\\n Table,\\n #[sea_orm(iden = \\"AfternoonTea\\")]\\n EverydayTea,\\n}\\n```\\n\\n## New Release ~~Train~~ Ferry \ud83d\udea2\\n\\nIt\'s been the **12th** release of SeaORM! Initially, a major version was released every month. It gradually became 2 to 3 months, and now, it\'s been 6 months since the last major release. As our userbase grew and some are already [using SeaORM in production](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#startups), we understand the importance of having a stable API surface and feature set.\\n\\nThat\'s why we are committed to:\\n\\n1. Reviewing breaking changes with strict scrutiny\\n2. Expanding our test suite to cover all features of our library\\n3. Never remove features, and consider deprecation carefully\\n\\nToday, the architecture of SeaORM is pretty solid and stable, and with the `0.12` release where we paid back a lot of technical debt, we will be able to deliver new features and enhancements without breaking. As our major dependency [SQLx](https://github.com/launchbadge/sqlx) is not `1.0` yet, technically we cannot be `1.0`.\\n\\nWe are still advancing rapidly, and we will always make a new release as soon as SQLx makes a new release, so that you can upgrade everything at once. As a result, the next major release of SeaORM will come out **6 months from now, or when SQLx makes a new release**, whichever is earlier.\\n\\n## [Community Survey](https://www.sea-ql.org/community-survey) \ud83d\udcdd\\n\\nSeaQL is an independent open-source organization. Our goal is to enable developers to build data intensive applications in Rust. If you are using SeaORM, please participate in the [SeaQL Community Survey](https://www.sea-ql.org/community-survey)!\\n\\nBy completing this survey, you will help us gather insights into how you, the developer, are using our libraries and identify means to improve your developer experience. We will also publish an annual survey report to summarize our findings.\\n\\nIf you are a happy user of SeaORM, consider [writing us a testimonial](https://forms.office.com/r/YbeqfTAgkJ)!\\n\\n## Sponsor \ud83e\udd47\\n\\nA big thank to [DigitalOcean](https://www.digitalocean.com/) who sponsored our server hosting, and [JetBrains](https://www.jetbrains.com/) who sponsored our IDE, and every sponsor on [GitHub Sponsor](https://github.com/sponsors/SeaQL)!\\n\\nIf you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Afonso Barracha\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Jacob Trueb\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Natsuki Ikeguchi\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marlon Mueller-Soppart\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n KallyDev\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Manfred Lee\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Roland Gor\xe1cz\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n IceApinan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ren\xe9 Kla\u010dan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## What\'s Next for SeaORM? \u26f5\\n\\nOpen-source project is a never-ending work, and we are actively looking for ways to sustain the project. You can support our endeavour by starring & sharing our repositories and becoming a sponsor.\\n\\nWe are considering multiple directions to generate revenue for the organization. If you have any suggestion, or want to join or collaborate with us, please contact us via `hello[at]sea-ql.org`.\\n\\nThank you for your support, and together we can make open-source sustainable."},{"id":"2023-04-03-intro-sea-streamer","metadata":{"permalink":"/blog/2023-04-03-intro-sea-streamer","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-04-03-intro-sea-streamer.md","source":"@site/blog/2023-04-03-intro-sea-streamer.md","title":"Introducing SeaStreamer \ud83c\udf0a","description":"We are pleased to introduce SeaStreamer to the Rust community today. SeaStreamer is a stream processing toolkit to help you build stream processors in Rust.","date":"2023-04-03T00:00:00.000Z","formattedDate":"April 3, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.915,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2023-04-03-intro-sea-streamer","title":"Introducing SeaStreamer \ud83c\udf0a","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Announcing SeaORM 0.12 \ud83d\udc1a","permalink":"/blog/2023-08-12-announcing-seaorm-0.12"},"nextItem":{"title":"What\'s new in SeaORM 0.11.0","permalink":"/blog/2023-02-08-whats-new-in-seaorm-0.11.0"}},"content":"\\n\\nWe are pleased to introduce [SeaStreamer](https://github.com/SeaQL/sea-streamer/) to the Rust community today. SeaStreamer is a stream processing toolkit to help you build stream processors in Rust.\\n\\nAt SeaQL we want to make Rust the best programming platform for data engineering. Where SeaORM is the essential tool for working with SQL databases, SeaStreamer aims to be your essential toolkit for working with streams.\\n\\nCurrently SeaStreamer provides integration with Kafka and Redis.\\n\\nLet\'s have a quick tour of SeaStreamer.\\n\\n## High level async API\\n\\n+ High level async API that supports both `async-std` and `tokio`\\n+ Mutex-free implementation[^1]: concurrency achieved by message passing\\n+ A comprehensive type system that guides/restricts you with the API\\n\\n[^1]: except `sea-streamer-stdio`, but only contends on consumer add/drop\\n\\nBelow is a [basic Kafka consumer](https://github.com/SeaQL/sea-streamer/blob/main/sea-streamer-kafka/src/bin/consumer.rs):\\n\\n```rust\\n#[tokio::main]\\nasync fn main() -> Result<()> {\\n env_logger::init();\\n\\n let stream: StreamUrl = \\"kafka://streamer.sea-ql.org:9092/my_stream\\".parse()?;\\n let streamer = KafkaStreamer::connect(stream.streamer(), Default::default()).await?;\\n let mut options = KafkaConsumerOptions::new(ConsumerMode::RealTime);\\n options.set_auto_offset_reset(AutoOffsetReset::Earliest);\\n let consumer = streamer\\n .create_consumer(stream.stream_keys(), options)\\n .await?;\\n\\n loop {\\n let mess = consumer.next().await?;\\n println!(\\"{}\\", mess.message().as_str()?);\\n }\\n}\\n```\\n\\n[`Consumer::stream()`](https://docs.rs/sea-streamer/latest/sea_streamer/trait.Consumer.html#tymethod.stream) returns an object that implements the [`Stream`](https://docs.rs/futures-core/latest/futures_core/stream/trait.Stream.html) trait, which allows you to do neat things:\\n\\n```rust\\nlet items = consumer\\n .stream()\\n .take(num)\\n .map(process_message)\\n .collect::>()\\n .await\\n```\\n\\n## Trait-based abstract interface\\n\\nAll SeaStreamer backends implement a common abstract interface, offering you a familiar API. Below is a [basic Redis consumer](https://github.com/SeaQL/sea-streamer/blob/main/sea-streamer-redis/src/bin/consumer.rs), which is nearly the same as the previous example:\\n\\n```rust\\n#[tokio::main]\\nasync fn main() -> Result<()> {\\n env_logger::init();\\n\\n let stream: StreamUrl = \\"redis://localhost:6379/my_stream\\".parse()?;\\n let streamer = RedisStreamer::connect(stream.streamer(), Default::default()).await?;\\n let mut options = RedisConsumerOptions::new(ConsumerMode::RealTime);\\n options.set_auto_stream_reset(AutoStreamReset::Earliest);\\n let consumer = streamer\\n .create_consumer(stream.stream_keys(), options)\\n .await?;\\n\\n loop {\\n let mess = consumer.next().await?;\\n println!(\\"{}\\", mess.message().as_str()?);\\n }\\n}\\n```\\n\\n## Redis Streams Support\\n\\nSeaStreamer Redis provides a Kafka-like stream semantics:\\n\\n+ Non-group streaming with AutoStreamReset option\\n+ Consumer-group-based streaming with auto-ack and/or auto-commit\\n+ Load balancing among consumers with automatic failover\\n+ Seek/rewind to point in time\\n\\nYou don\'t have to call `XADD`, `XREAD`, `XACK`, etc... anymore!\\n\\n## Enum-based generic interface\\n\\nThe trait-based API requires you to designate the concrete `Streamer` type for monomorphization, otherwise the code cannot compile.\\n\\nAkin to how SeaORM implements runtime-polymorphism, SeaStreamer provides a enum-based generic streamer, in which the backend is selected ***on runtime***.\\n\\nHere is an illustration ([full example](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/resumable.rs)):\\n\\n```rust\\n// sea-streamer-socket\\npub struct SeaConsumer {\\n backend: SeaConsumerBackend,\\n}\\n\\nenum SeaConsumerBackend {\\n #[cfg(feature = \\"backend-kafka\\")]\\n Kafka(KafkaConsumer),\\n #[cfg(feature = \\"backend-redis\\")]\\n Redis(RedisConsumer),\\n #[cfg(feature = \\"backend-stdio\\")]\\n Stdio(StdioConsumer),\\n}\\n\\n// Your code\\nlet uri: StreamerUri = \\"kafka://localhost:9092\\".parse()?; // or\\nlet uri: StreamerUri = \\"redis://localhost:6379\\".parse()?; // or\\nlet uri: StreamerUri = \\"stdio://\\".parse()?;\\n\\n// SeaStreamer will be backed by Kafka, Redis or Stdio depending on the URI\\nlet streamer = SeaStreamer::connect(uri, Default::default()).await?;\\n\\n// Set backend-specific options\\nlet mut options = SeaConsumerOptions::new(ConsumerMode::Resumable);\\noptions.set_kafka_consumer_options(|options: &mut KafkaConsumerOptions| { .. });\\noptions.set_redis_consumer_options(|options: &mut RedisConsumerOptions| { .. });\\nlet mut consumer: SeaConsumer = streamer.create_consumer(stream_keys, options).await?;\\n\\n// You can still retrieve the concrete type\\nlet kafka: Option<&mut KafkaConsumer> = consumer.get_kafka();\\nlet redis: Option<&mut RedisConsumer> = consumer.get_redis();\\n```\\n\\nSo you can \\"write once, stream anywhere\\"!\\n\\n## Good old unix pipe\\n\\nIn SeaStreamer, `stdin` & `stdout` can be used as stream source and sink.\\n\\nSay you are developing some processors to transform a stream in several stages:\\n\\n```shell\\n./processor_1 --input kafka://localhost:9092/input --output kafka://localhost:9092/stage_1 &\\n./processor_2 --input kafka://localhost:9092/stage_1 --output kafka://localhost:9092/stage_2 &\\n./processor_3 --input kafka://localhost:9092/stage_2 --output kafka://localhost:9092/output &\\n```\\n\\nIt would be great if we can simply ***pipe*** the processors together right?\\n\\nWith SeaStreamer, you can do the following:\\n\\n```shell\\n./processor_1 --input kafka://localhost:9092/input --output stdio:///stream |\\n./processor_2 --input stdio:///stream --output stdio:///stream |\\n./processor_3 --input stdio:///stream --output kafka://localhost:9092/output\\n```\\n\\nAll ***without recompiling*** the stream processors! Now, you can develop locally with the comfort of using `|`, `>`, `<` and your favourite unix program in the shell.\\n\\n## Testable\\n\\nSeaStreamer encourages you to write tests at all levels:\\n\\n+ You can execute tests involving several *stream processors* in the same *OS process*\\n+ You can execute tests involving several *OS processes* by connecting them with pipes\\n+ You can execute tests involving several *stream processors* with Redis / Kafka\\n\\nAll against the same piece of code! Let SeaStreamer take away the boilerplate and mocking facility from your codebase.\\n\\nBelow is an example of [intra-process testing](https://github.com/SeaQL/sea-streamer/blob/main/sea-streamer-stdio/tests/loopback.rs), which can be run with `cargo test` without any dependency or side-effects:\\n\\n```rust\\nlet stream = StreamKey::new(\\"test\\")?;\\nlet mut options = StdioConnectOptions::default();\\noptions.set_loopback(true); // messages produced will be feed back to consumers\\nlet streamer = StdioStreamer::connect(StreamerUri::zero(), options).await?;\\nlet producer = streamer.create_producer(stream.clone(), Default::default()).await?;\\nlet mut consumer = streamer.create_consumer(&[stream.clone()], Default::default()).await?;\\n\\nfor i in 0..5 {\\n let mess = format!(\\"{}\\", i);\\n producer.send(mess)?;\\n}\\n\\nlet seq = collect(&mut consumer, 5).await;\\nassert_eq!(seq, [0, 1, 2, 3, 4]);\\n```\\n\\n## Getting started\\n\\nIf you are eager to get started with SeaStreamer, you can checkout our [set of examples](https://github.com/SeaQL/sea-streamer/tree/main/examples):\\n\\n+ [`consumer`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/consumer.rs): A basic consumer\\n+ [`producer`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/producer.rs): A basic producer\\n+ [`processor`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/processor.rs): A basic stream processor\\n+ [`resumable`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/resumable.rs): A resumable stream processor that continues from where it left off\\n+ [`buffered`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/buffered.rs): An advanced stream processor with internal buffering and batch processing\\n+ [`blocking`](https://github.com/SeaQL/sea-streamer/blob/main/examples/src/bin/blocking.rs): An advanced stream processor for handling blocking / CPU-bound tasks\\n\\nRead the official [documentation](https://www.sea-ql.org/SeaStreamer/docs/index/) to learn more.\\n\\n## Roadmap\\n\\nA few major components we [plan to develop](https://www.sea-ql.org/SeaStreamer/docs/whats-next/roadmap/):\\n\\n+ File Backend\\n+ Redis Cluster\\n\\nWe welcome you to join our [Discussions](https://github.com/SeaQL/sea-streamer/discussions) if you have thoughts or ideas!\\n\\n## People\\n\\nSeaStreamer is designed and developed by the same mind who brought you [SeaORM](https://github.com/SeaQL/sea-orm/):\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL.org is an independent open-source organization run by passionate \ufe0fdevelopers. If you like our projects, please star \u2b50 and share our repositories. If you feel generous, a small donation via [GitHub Sponsor](https://github.com/sponsors/SeaQL) will be greatly appreciated, and goes a long way towards sustaining the organization \ud83d\udea2.\\n\\nSeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future \ud83e\udd80."},{"id":"2023-02-08-whats-new-in-seaorm-0.11.0","metadata":{"permalink":"/blog/2023-02-08-whats-new-in-seaorm-0.11.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-02-08-whats-new-in-seaorm-0.11.0.md","source":"@site/blog/2023-02-08-whats-new-in-seaorm-0.11.0.md","title":"What\'s new in SeaORM 0.11.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.11.0!","date":"2023-02-08T00:00:00.000Z","formattedDate":"February 8, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":9.18,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2023-02-08-whats-new-in-seaorm-0.11.0","title":"What\'s new in SeaORM 0.11.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Introducing SeaStreamer \ud83c\udf0a","permalink":"/blog/2023-04-03-intro-sea-streamer"},"nextItem":{"title":"SeaORM FAQ.02","permalink":"/blog/2023-02-05-faq-02"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.11.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.11.0)!\\n\\n## Data Loader\\n\\n[[#1443](https://github.com/SeaQL/sea-orm/pull/1443), [#1238](https://github.com/SeaQL/sea-orm/pull/1238)] The [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) provides an API to load related entities in batches.\\n\\nConsider this one to many relation:\\n\\n```rust\\nlet cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find()\\n .find_with_related(Fruit)\\n .all(db)\\n .await?;\\n```\\n\\nThe generated SQL is:\\n\\n```sql\\nSELECT\\n \\"cake\\".\\"id\\" AS \\"A_id\\",\\n \\"cake\\".\\"name\\" AS \\"A_name\\",\\n \\"fruit\\".\\"id\\" AS \\"B_id\\",\\n \\"fruit\\".\\"name\\" AS \\"B_name\\",\\n \\"fruit\\".\\"cake_id\\" AS \\"B_cake_id\\"\\nFROM \\"cake\\"\\nLEFT JOIN \\"fruit\\" ON \\"cake\\".\\"id\\" = \\"fruit\\".\\"cake_id\\"\\nORDER BY \\"cake\\".\\"id\\" ASC\\n```\\n\\nThe 1 side\'s (Cake) data will be duplicated. If N is a large number, this would results in more data being transferred over the wire. Using the Loader would ensure each model is transferred only once.\\n\\nThe following loads the same data as above, but with two queries:\\n\\n```rust\\nlet cakes: Vec = Cake::find().all(db).await?;\\nlet fruits: Vec> = cakes.load_many(Fruit, db).await?;\\n\\nfor (cake, fruits) in cakes.into_iter().zip(fruits.into_iter()) { .. }\\n```\\n\\n```sql\\nSELECT \\"cake\\".\\"id\\", \\"cake\\".\\"name\\" FROM \\"cake\\"\\nSELECT \\"fruit\\".\\"id\\", \\"fruit\\".\\"name\\", \\"fruit\\".\\"cake_id\\" FROM \\"fruit\\" WHERE \\"fruit\\".\\"cake_id\\" IN (..)\\n```\\n\\nYou can even apply filters on the related entity:\\n\\n```rust\\nlet fruits_in_stock: Vec> = cakes.load_many(\\n fruit::Entity::find().filter(fruit::Column::Stock.gt(0i32))\\n db\\n).await?;\\n```\\n\\n```sql\\nSELECT \\"fruit\\".\\"id\\", \\"fruit\\".\\"name\\", \\"fruit\\".\\"cake_id\\" FROM \\"fruit\\"\\nWHERE \\"fruit\\".\\"stock\\" > 0 AND \\"fruit\\".\\"cake_id\\" IN (..)\\n```\\n\\nTo learn more, read the [relation docs](https://www.sea-ql.org/SeaORM/docs/relation/data-loader/).\\n\\n## Transaction Isolation Level and Access Mode\\n\\n[[#1230](https://github.com/SeaQL/sea-orm/pull/1230)] The [`transaction_with_config`](https://docs.rs/sea-orm/*/sea_orm/trait.TransactionTrait.html#tymethod.transaction_with_config) and [`begin_with_config`](https://docs.rs/sea-orm/*/sea_orm/trait.TransactionTrait.html#tymethod.begin_with_config) allows you to specify the [IsolationLevel](https://docs.rs/sea-orm/*/sea_orm/enum.IsolationLevel.html) and [AccessMode](https://docs.rs/sea-orm/*/sea_orm/enum.AccessMode.html).\\n\\nFor now, they are only implemented for MySQL and Postgres. In order to align their semantic difference, MySQL will execute `SET TRANSACTION` commands before begin transaction, while Postgres will execute `SET TRANSACTION` commands after begin transaction.\\n\\n```rust\\ndb.transaction_with_config::<_, _, DbErr>(\\n |txn| { ... },\\n Some(IsolationLevel::ReadCommitted),\\n Some(AccessMode::ReadOnly),\\n)\\n.await?;\\n\\nlet transaction = db\\n .begin_with_config(IsolationLevel::ReadCommitted, AccessMode::ReadOnly)\\n .await?;\\n```\\n\\nTo learn more, read the [transaction docs](https://www.sea-ql.org/SeaORM/docs/advanced-query/transaction/).\\n\\n## Cast Column Type on Select and Save\\n\\n[[#1304](https://github.com/SeaQL/sea-orm/pull/1304)] If you need to select a column as one type but save it into the database as another, you can specify the `select_as` and the `save_as` attributes to perform the casting. A typical use case is selecting a column of type `citext` (case-insensitive text) as `String` in Rust and saving it into the database as `citext`. One should define the model field as below:\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"ci_table\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n #[sea_orm(select_as = \\"text\\", save_as = \\"citext\\")]\\n pub case_insensitive_text: String\\n}\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\\npub enum Relation {}\\n\\nimpl ActiveModelBehavior for ActiveModel {}\\n```\\n\\n## Changes to `ActiveModelBehavior`\\n\\n[[#1328](https://github.com/SeaQL/sea-orm/pull/1328), [#1145](https://github.com/SeaQL/sea-orm/pull/1145)] The methods of `ActiveModelBehavior` now have `Connection` as an additional parameter. It enables you to perform database operations, for example, logging the changes made to the existing model or validating the data before inserting it.\\n\\n```rust\\n#[async_trait]\\nimpl ActiveModelBehavior for ActiveModel {\\n /// Create a new ActiveModel with default values. Also used by `Default::default()`.\\n fn new() -> Self {\\n Self {\\n uuid: Set(Uuid::new_v4()),\\n ..ActiveModelTrait::default()\\n }\\n }\\n\\n /// Will be triggered before insert / update\\n async fn before_save(self, db: &C, insert: bool) -> Result\\n where\\n C: ConnectionTrait,\\n {\\n // Logging changes\\n edit_log::ActiveModel {\\n action: Set(\\"before_save\\".into()),\\n values: Set(serde_json::json!(model)),\\n ..Default::default()\\n }\\n .insert(db)\\n .await?;\\n\\n Ok(self)\\n }\\n}\\n```\\n\\nTo learn more, read the [entity docs](https://www.sea-ql.org/SeaORM/docs/generate-entity/entity-structure/#active-model-behavior).\\n\\n## Execute Unprepared SQL Statement\\n\\n[[#1327](https://github.com/SeaQL/sea-orm/pull/1327)] You can execute an unprepared SQL statement with [`ConnectionTrait::execute_unprepared`](https://docs.rs/sea-orm/*/sea_orm/trait.ConnectionTrait.html#tymethod.execute_unprepared).\\n\\n```rust\\n// Use `execute_unprepared` if the SQL statement doesn\'t have value bindings\\ndb.execute_unprepared(\\n \\"CREATE TABLE `cake` (\\n `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,\\n `name` varchar(255) NOT NULL\\n )\\"\\n)\\n.await?;\\n\\n// Construct a `Statement` if the SQL contains value bindings\\nlet stmt = Statement::from_sql_and_values(\\n manager.get_database_backend(),\\n r#\\"INSERT INTO `cake` (`name`) VALUES (?)\\"#,\\n [\\"Cheese Cake\\".into()]\\n);\\ndb.execute(stmt).await?;\\n```\\n\\n## Select Into Tuple\\n\\n[[#1311](https://github.com/SeaQL/sea-orm/pull/1311)] You can select a tuple (or single value) with the [`into_tuple`](https://docs.rs/sea-orm/*/sea_orm/struct.Selector.html#method.into_tuple) method.\\n\\n```rust\\nlet res: Vec<(String, i64)> = cake::Entity::find()\\n .select_only()\\n .column(cake::Column::Name)\\n .column(cake::Column::Id.count())\\n .group_by(cake::Column::Name)\\n .into_tuple()\\n .all(&db)\\n .await?;\\n```\\n\\n## Atomic Migration\\n\\n[[#1379](https://github.com/SeaQL/sea-orm/pull/1379)] Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite.\\n\\nYou can start a transaction inside each migration to perform operations like [seeding sample data](https://www.sea-ql.org/SeaORM/docs/migration/seeding-data/#seeding-data-transactionally) for a newly created table.\\n\\n## Types Support\\n\\n* [[#1325](https://github.com/SeaQL/sea-orm/pull/1325)] Support various UUID formats that are available in `uuid::fmt` module\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"uuid_fmt\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub uuid: Uuid,\\n pub uuid_braced: uuid::fmt::Braced,\\n pub uuid_hyphenated: uuid::fmt::Hyphenated,\\n pub uuid_simple: uuid::fmt::Simple,\\n pub uuid_urn: uuid::fmt::Urn,\\n}\\n```\\n\\n* [[#1210](https://github.com/SeaQL/sea-orm/pull/1210)] Support vector of enum for Postgres\\n\\n```rust\\n#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\\n#[sea_orm(rs_type = \\"String\\", db_type = \\"Enum\\", enum_name = \\"tea\\")]\\npub enum Tea {\\n #[sea_orm(string_value = \\"EverydayTea\\")]\\n EverydayTea,\\n #[sea_orm(string_value = \\"BreakfastTea\\")]\\n BreakfastTea,\\n}\\n\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"enum_vec\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub teas: Vec,\\n pub teas_opt: Option>,\\n}\\n```\\n\\n* [[#1414](https://github.com/SeaQL/sea-orm/pull/1414)] Support `ActiveEnum` field as primary key\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"enum_primary_key\\")]\\npub struct Model {\\n #[sea_orm(primary_key, auto_increment = false)]\\n pub id: Tea,\\n pub category: Option,\\n pub color: Option,\\n}\\n```\\n\\n## Opt-in Unstable Internal APIs\\n\\nBy enabling `sea-orm-internal` feature you opt-in unstable internal APIs including:\\n\\n* Accessing the inner connection pool of SQLx with [`get_*_connection_pool`](https://docs.rs/sea-orm/*/sea_orm/enum.DatabaseConnection.html#impl-DatabaseConnection-2) method\\n* Re-exporting [SQLx errors types](https://docs.rs/sea-orm/*/sea_orm/error/index.html): `SqlxError`, `SqlxMySqlError`, `SqlxPostgresError` and `SqlxSqliteError`\\n\\n## Breaking Changes\\n\\n* [[#1366](https://github.com/SeaQL/sea-orm/pull/1366)] `sea-query` has been upgraded to [`0.28.x`](https://github.com/SeaQL/sea-query/releases/tag/0.28.0), which comes with some improvements and breaking changes. Please follow the release notes for more details\\n\\n* [[#1420](https://github.com/SeaQL/sea-orm/pull/1420)] sea-orm-cli: `generate entity` command enable `--universal-time` flag by default\\n\\n* [[#1425](https://github.com/SeaQL/sea-orm/pull/1425)] Added `RecordNotInserted` and `RecordNotUpdated` to `DbErr`\\n\\n* [[#1327](https://github.com/SeaQL/sea-orm/pull/1327)] Added `ConnectionTrait::execute_unprepared` method\\n\\n* [[#1311](https://github.com/SeaQL/sea-orm/pull/1311)] The required method of `TryGetable` changed:\\n\\n```rust\\n// then\\nfn try_get(res: &QueryResult, pre: &str, col: &str) -> Result;\\n// now; ColIdx can be `&str` or `usize`\\nfn try_get_by(res: &QueryResult, index: I) -> Result;\\n```\\n\\nSo if you implemented it yourself:\\n\\n```diff\\nimpl TryGetable for XXX {\\n- fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result {\\n+ fn try_get_by(res: &QueryResult, idx: I) -> Result {\\n- let value: YYY = res.try_get(pre, col).map_err(TryGetError::DbErr)?;\\n+ let value: YYY = res.try_get_by(idx).map_err(TryGetError::DbErr)?;\\n ..\\n }\\n}\\n```\\n\\n* [[#1328](https://github.com/SeaQL/sea-orm/pull/1328)] The `ActiveModelBehavior` trait becomes async trait.\\nIf you overridden the default `ActiveModelBehavior` implementation:\\n\\n```rust\\n#[async_trait::async_trait]\\nimpl ActiveModelBehavior for ActiveModel {\\n async fn before_save(self, db: &C, insert: bool) -> Result\\n where\\n C: ConnectionTrait,\\n {\\n // ...\\n }\\n\\n // ...\\n}\\n```\\n\\n* [[#1425](https://github.com/SeaQL/sea-orm/pull/1425)] `DbErr::RecordNotFound(\\"None of the database rows are affected\\")` is moved to a dedicated error variant `DbErr::RecordNotUpdated`\\n\\n```rust\\nlet res = Update::one(cake::ActiveModel {\\n name: Set(\\"Cheese Cake\\".to_owned()),\\n ..model.into_active_model()\\n })\\n .exec(&db)\\n .await;\\n\\n// then\\nassert_eq!(\\n res,\\n Err(DbErr::RecordNotFound(\\n \\"None of the database rows are affected\\".to_owned()\\n ))\\n);\\n\\n// now\\nassert_eq!(res, Err(DbErr::RecordNotUpdated));\\n```\\n\\n* [[#1395](https://github.com/SeaQL/sea-orm/pull/1395)] `sea_orm::ColumnType` was replaced by `sea_query::ColumnType`\\n * Method `ColumnType::def` was moved to `ColumnTypeTrait`\\n * `ColumnType::Binary` becomes a tuple variant which takes in additional option `sea_query::BlobSize`\\n * `ColumnType::Custom` takes a `sea_query::DynIden` instead of `String` and thus a new method `custom` is added (note the lowercase)\\n\\n```diff\\n// Compact Entity\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"fruit\\")]\\npub struct Model {\\n- #[sea_orm(column_type = r#\\"Custom(\\"citext\\".to_owned())\\"#)]\\n+ #[sea_orm(column_type = r#\\"custom(\\"citext\\")\\"#)]\\n pub column: String,\\n}\\n```\\n\\n```diff\\n// Expanded Entity\\nimpl ColumnTrait for Column {\\n type EntityName = Entity;\\n\\n fn def(&self) -> ColumnDef {\\n match self {\\n- Self::Column => ColumnType::Custom(\\"citext\\".to_owned()).def(),\\n+ Self::Column => ColumnType::custom(\\"citext\\").def(),\\n }\\n }\\n}\\n```\\n\\n## SeaORM Enhancements\\n\\n* [[#1256](https://github.com/SeaQL/sea-orm/pull/1256)] Refactor schema module to expose functions for database alteration\\n* [[#1346](https://github.com/SeaQL/sea-orm/pull/1346)] Generate compact entity with `#[sea_orm(column_type = \\"JsonBinary\\")]` macro attribute\\n* `MockDatabase::append_exec_results()`, `MockDatabase::append_query_results()`, `MockDatabase::append_exec_errors()` and `MockDatabase::append_query_errors()` [[#1367](https://github.com/SeaQL/sea-orm/pull/1367)] take any types implemented `IntoIterator` trait\\n* [[#1362](https://github.com/SeaQL/sea-orm/pull/1362)] `find_by_id` and `delete_by_id` take any `Into` primary key value\\n* [[#1410](https://github.com/SeaQL/sea-orm/pull/1410)] `QuerySelect::offset` and `QuerySelect::limit` takes in `Into>` where `None` would reset them\\n* [[#1236](https://github.com/SeaQL/sea-orm/pull/1236)] Added `DatabaseConnection::close`\\n* [[#1381](https://github.com/SeaQL/sea-orm/pull/1381)] Added `is_null` getter for `ColumnDef`\\n* [[#1177](https://github.com/SeaQL/sea-orm/pull/1177)] Added `ActiveValue::reset` to convert `Unchanged` into `Set`\\n* [[#1415](https://github.com/SeaQL/sea-orm/pull/1415)] Added `QueryTrait::apply_if` to optionally apply a filter\\n* Added the `sea-orm-internal` feature flag to expose some SQLx types\\n * [[#1297](https://github.com/SeaQL/sea-orm/pull/1297)] Added `DatabaseConnection::get_*_connection_pool()` for accessing the inner SQLx connection pool\\n * [[#1434](https://github.com/SeaQL/sea-orm/pull/1434)] Re-exporting SQLx errors\\n\\n## CLI Enhancements\\n\\n* [[#846](https://github.com/SeaQL/sea-orm/pull/846), [#1186](https://github.com/SeaQL/sea-orm/pull/1186), [#1318](https://github.com/SeaQL/sea-orm/pull/1318)] Generate `#[serde(skip_deserializing)]` for primary key columns\\n* [[#1171](https://github.com/SeaQL/sea-orm/pull/1171), [#1320](https://github.com/SeaQL/sea-orm/pull/1320)] Generate `#[serde(skip)]` for hidden columns\\n* [[#1124](https://github.com/SeaQL/sea-orm/pull/1124), [#1321](https://github.com/SeaQL/sea-orm/pull/1321)] Generate entity with extra derives and attributes for model struct\\n\\n## Integration Examples\\n\\nSeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples [wanted](https://github.com/SeaQL/sea-orm/issues/269)!\\n\\n* [Actix v4 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)\\n* [Actix v3 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example)\\n* [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)\\n* [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example)\\n* [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example)\\n* [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)\\n* [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)\\n* [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example)\\n* [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example)\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! SeaQL.org is an independent open-source organization run by passionate developers. If you enjoy using SeaORM, please star and share our repositories. If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the project.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Afonso Barracha\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Nick Price\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Roland Gor\xe1cz\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Henrik Giesel\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Jacob Trueb\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Naoki Ikeguchi\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Manfred Lee\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marcus Buffett\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n efrain2007\\n
\\n
\\n
\\n
\\n
\\n\\n## What\'s Next?\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and build together for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.12.x`](https://github.com/SeaQL/sea-orm/milestone/12)."},{"id":"2023-02-05-faq-02","metadata":{"permalink":"/blog/2023-02-05-faq-02","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-02-05-faq-02.md","source":"@site/blog/2023-02-05-faq-02.md","title":"SeaORM FAQ.02","description":"FAQ.02 Why the empty enum Relation {} is needed even if an Entity has no relations?","date":"2023-02-05T00:00:00.000Z","formattedDate":"February 5, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":1.225,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2023-02-05-faq-02","title":"SeaORM FAQ.02","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.11.0","permalink":"/blog/2023-02-08-whats-new-in-seaorm-0.11.0"},"nextItem":{"title":"Internship @ SeaQL","permalink":"/blog/2023-01-28-internship-at-seaql"}},"content":"## FAQ.02 Why the empty enum `Relation {}` is needed even if an Entity has no relations?\\n\\nConsider the following example [Post](https://github.com/SeaQL/sea-orm/blob/master/examples/actix_example/entity/src/post.rs) Entity:\\n\\n```rust\\nuse sea_orm::entity::prelude::*;\\n\\n#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"posts\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub title: String,\\n pub text: String,\\n}\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\\npub enum Relation {}\\n\\nimpl ActiveModelBehavior for ActiveModel {}\\n```\\n\\nThe two lines for defining `Relation` is quite unnecessary right?\\n\\nTo explain the problem, let\'s dive slightly deeper into the macro-expanded entity:\\n\\nThe `DeriveRelation` macro simply implements the `RelationTrait`:\\n\\n```rust\\nimpl RelationTrait for Relation {\\n fn def(&self) -> RelationDef {\\n match self {\\n _ => unreachable!()\\n }\\n }\\n}\\n```\\n\\nWhich in turn is needed by `EntityTrait` as an associated type:\\n\\n```rust\\nimpl EntityTrait for Entity {\\n type Relation = Relation;\\n ...\\n}\\n```\\n\\nIt would be ideal if, when the user does not specify this associated type, the library automatically fills in a stub to satisfy the type system?\\n\\nTurns out, there is such a feature in Rust! It is an unstable feature called [`associated_type_defaults`](https://rust-lang.github.io/rfcs/2532-associated-type-defaults.html).\\n\\nBasically, it allows trait definitions to specify a default associated type, allowing it to be elided:\\n\\n```rust\\n// only compiles in nightly\\ntrait EntityTrait {\\n type Relation: Relation = EmptyRelation;\\n}\\n```\\n\\nDue to our commitment to stable Rust, this may not land in SeaORM very soon. When it is stabilized, do remind us to implement this feature to get rid of those two lines!"},{"id":"2023-01-28-internship-at-seaql","metadata":{"permalink":"/blog/2023-01-28-internship-at-seaql","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-01-28-internship-at-seaql.md","source":"@site/blog/2023-01-28-internship-at-seaql.md","title":"Internship @ SeaQL","description":"SeaQL.org offer internships tailored to university students. In fact, it will be the 3rd cohort in 2023.","date":"2023-01-28T00:00:00.000Z","formattedDate":"January 28, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.15,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2023-01-28-internship-at-seaql","title":"Internship @ SeaQL","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"SeaORM FAQ.02","permalink":"/blog/2023-02-05-faq-02"},"nextItem":{"title":"Call for Contributors and Reviewers \ud83d\udce2","permalink":"/blog/2023-01-01-call-for-contributors-n-reviewers"}},"content":"SeaQL.org offer internships tailored to university students. In fact, it will be the 3rd cohort in 2023.\\n\\nThe internships normally take place during summer and winter semester breaks. During the internship period, you will work on a project dedicatedly and publish the project\u2019s outcome at the end.\\n\\nThe striking aspect of our mode of operation is it covers the entire lifecycle of software development, from Design \u27a1\ufe0f Implementation \u27a1\ufe0f Testing \u27a1\ufe0f Delivery. You will be amazed of how much you can achieve in such a short period of time!\\n\\nTo date [StarfishQL \u2734\ufe0f](https://www.sea-ql.org/StarfishQL/) and [Seaography \ud83e\udded](https://www.sea-ql.org/Seaography/) are great projects our team has created in the past year. We pride ourselves on careful planning, consistent execution, and pragmatic approach in software engineering. I spend a huge amount of time on idea evaluation: if the scope of the project is too small, it will be uninteresting, but if it is too large, it will fail to be delivered.\\n\\nFellow undergraduates, here are a few good reasons why you should participate in internships at an open-source organization like SeaQL:\\n\\n1.\\tA tangible showcase on CV: open-source work is published, inspectable and has real-world impact. We will also ensure that it has good branding, graphics, and visibility.\\n2.\\tNot driven by a business process, we do not compromise on quality of work. We do not have a proprietary development process, so it\u2019s all open-source tools with transferable skills.\\n3.\\tYou will contribute to the community and will interact with people across the world. Collaboration on open source is the best thing humanity ever invented. You will only believe me when you have experienced it first-hand.\\n4.\\tBecause you are the driver of the project you work on, it allows you to uncover something more about yourself, in particular - abilities and discipline: you always have had under/over-estimated yourself in one aspect or another.\\n\\nHere are several things you are going to learn:\\n\\n1.\\t\\"Thinking > Programming\\": the more time you spend on thinking beforehand, the better the code you are going to write. And the more time you spend on reviewing afterwards, the better the code is going to be.\\n2.\\tHow to organize a codebase. Make good use of the Rust type system to craft a modular, testable codebase.\\n3.\\tTest automation. Every SeaQL project is continuously tested, and this is an integral part of our engineering process.\\n4.\\tDocumentation. Our software aims to provide good documentation that is comprehensive, easy to follow, and fun to read.\\n5.\\tPerformance tuning. Depending on the project, we might do some benchmarking and optimization. But in general we put you off from writing code that creates unnecessary overhead.\\n\\nWe were a mentor organization in [GSoC 2022](https://summerofcode.withgoogle.com/programs/2022/organizations/seaql) and may be in 2023 (update: we were not accepted into GSoC 2023). We also offer internships outside of GSoC. So, what are the requirements when you become a contributor?\\n\\n1.\\tBe passionate. You must show your passion in open-source and software engineering, so a good GitHub profile with some participation is needed.\\n2.\\tBe dedicated. This is a full-time job. While being fully remote and flexible on hours, you must have no other commitment or duties during the stipulated internship period.\\n3.\\tBe open-minded. You should listen carefully to your mentors and act on their advice accordingly.\\n4.\\tWrite more. Communicate your thoughts and progress on all channels in an organized manner.\\n\\nDon\u2019t just listen to me though. Here is what our past interns says:\\n\\n+ [Sanford Pun](https://github.com/shpun817) - in the team that created StarfishQL \u2734\ufe0f: [Interning at Vision Cortex and SeaQL](https://shpun817.github.io/2023/01/21/interning-at-vision-cortex-and-seaql/)\\n+ [Panagiotis Karatakis](https://github.com/karatakis) - in the team that created Seaography \ud83e\udded: [My GSoC Experience](https://karatakis.com/blog/my-gsoc-experience)\\n\\nBe well-prepared for your upcoming career in this technology industry! Follow us on [GitHub](https://github.com/SeaQL/) and [Twitter](https://twitter.com/sea_ql) now, and stay tuned for future announcements."},{"id":"2023-01-01-call-for-contributors-n-reviewers","metadata":{"permalink":"/blog/2023-01-01-call-for-contributors-n-reviewers","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2023-01-01-call-for-contributors-n-reviewers.md","source":"@site/blog/2023-01-01-call-for-contributors-n-reviewers.md","title":"Call for Contributors and Reviewers \ud83d\udce2","description":"We are calling for contributors and reviewers for SeaQL projects \ud83d\udce2!","date":"2023-01-01T00:00:00.000Z","formattedDate":"January 1, 2023","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.945,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Founder","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2023-01-01-call-for-contributors-n-reviewers","title":"Call for Contributors and Reviewers \ud83d\udce2","author":"Chris Tsang","author_title":"SeaQL Founder","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Internship @ SeaQL","permalink":"/blog/2023-01-28-internship-at-seaql"},"nextItem":{"title":"What\'s new in SeaQuery 0.28.0","permalink":"/blog/2022-12-30-whats-new-in-seaquery-0.28.0"}},"content":"We are calling for contributors and reviewers for SeaQL projects \ud83d\udce2!\\n\\nThe SeaQL userbase has been steadily growing in the past year, and it\u2019s a pleasure for us to have helped individuals and start-ups to build their projects in Rust. However, the volume of questions, issues and pull requests is nearly saturating our core members\u2019 capacity.\\n\\nBut again, thank you everyone for participating in the community!\\n\\nIf your project depends on SeaQL and you want to help us, here are some suggestions (if you have not already, star [all our repositories](https://github.com/orgs/SeaQL/repositories?q=&type=all&language=&sort=stargazers) and follow us on [Twitter](https://twitter.com/sea_ql)):\\n1.\\tFinancial Contribution. You can [sponsor us on GitHub](https://github.com/sponsors/SeaQL) and those will be used to cover our expenses. As a courtesy, we listen to our sponsors for their needs and use cases, and we also communicate our organizational development from time-to-time.\\n2.\\tCode Contribution. Opening a PR with us is always appreciated! To get started, you can go through our issue trackers and pick one to handle. If you are thinking of developing a substantial feature, start with drafting a [\\"Proposal & Implementation Plan\\" (PIP)](https://github.com/SeaQL/sea-orm/issues?q=is%3Aissue+%5BPIP%5D).\\n3.\\tKnowledge Contribution. There are various formats of knowledge sharing: [tutorial](https://github.com/SeaQL/sea-orm-tutorial), [cookbook](https://github.com/SeaQL/sea-orm-cookbook), [QnA](https://github.com/SeaQL/sea-orm/discussions/categories/q-a) and Discord. You can open PRs to our [documentation](https://github.com/SeaQL/seaql.github.io) repositories or publish on your own. We will be happy to list it in our learning resources section. Keep an eye on our GitHub Discussions and Discord and help others where you can!\\n4.\\tCode Review. This is an important process of our engineering. Right now, only 3 of our core members serve as reviewers. Non-core members can also become reviewers and I invite you to become one!\\n\\nNow, I\u2019d like to outline our review policy: for maturing projects, each PR merged has to be approved by at least two reviewers and one of them must be a core member; self-review allowed. Here are some examples:\\n\\n+ A core member opened a PR, another core member approved \u2705 \\n+ A core member opened a PR, a reviewer approved \u2705\\n+ A reviewer opened a PR, a core member approved \u2705\\n+ A reviewer opened a PR, another reviewer approved \u26d4\\n+ A contributor opened a PR, 2 core members approved \u2705\\n+ A contributor opened a PR, a core member and a reviewer approved \u2705\\n+ A contributor opened a PR, 2 reviewers approved \u26d4\\n\\nIn a nutshell, at least two pairs of trusted eyes should have gone through each PR.\\n\\n### What are the criteria when reviewing a PR?\\n\\nThe following questions should all be answered yes.\\n\\n1.\\tImplementation, documentation and tests\\n 1.\\tIs the implementation easy to follow (have meaningful variable and function names)?\\n 2.\\tIs there sufficient document to the API?\\n 3.\\tAre there adequate tests covering various cases?\\n2.\\tAPI design\\n 1.\\tIs the API self-documenting so users can understand its use easily?\\n 2.\\tIs the API style consistent with our existing API?\\n 3.\\tDoes the API made reasonable use of the type system to enforce constraints?\\n 4.\\tAre the failure paths and error messages clear?\\n 5. Are all breaking changes justified and documented?\\n3.\\tFunctionality\\n 1.\\tDoes the feature make sense in computer science terms?\\n 2.\\tDoes the feature actually work with all our supported backends?\\n 3.\\tAre all caveats discussed and eliminated / documented?\\n4.\\tArchitecture\\n 1.\\tDoes it fit with the existing architecture of our codebase?\\n 2.\\tIs it not going to create technical debt / maintenance burden?\\n 3.\\tDoes it not break abstraction?\\n\\n1, 2 & 3 are fairly objective and factual, however the answers to 4 probably require some discussion and debate. If a consensus cannot be made, [@tyt2y3](https://github.com/tyt2y3) will make the final verdict.\\n\\n### Who are the current reviewers?\\n\\nAs of today, SeaQL has 3 core members who are also reviewers:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n Founder. Maintains all projects.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n Founding member. Co-maintainer of SeaORM and Seaography.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ivan Krivosheev\\n
\\n Joined in 2022. Co-maintainer of SeaQuery.\\n
\\n
\\n
\\n
\\n\\n### How to become a reviewer?\\n\\nWe are going to invite a few contributors we worked closely with, but you can also volunteer \u2013 the requirement is: you have made substantial code contribution to our projects, and has shown familiarity with our [engineering practices](https://www.sea-ql.org/blog/2022-07-30-engineering/).\\n\\nOver time, when you have made significant contribution to our organization, you can also become a core member.\\n\\n### Let\u2019s build for Rust\'s future together \ud83e\udd80"},{"id":"2022-12-30-whats-new-in-seaquery-0.28.0","metadata":{"permalink":"/blog/2022-12-30-whats-new-in-seaquery-0.28.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-12-30-whats-new-in-seaquery-0.28.0.md","source":"@site/blog/2022-12-30-whats-new-in-seaquery-0.28.0.md","title":"What\'s new in SeaQuery 0.28.0","description":"\ud83c\udf89 We are pleased to release SeaQuery 0.28.0! Here are some feature highlights \ud83c\udf1f:","date":"2022-12-30T00:00:00.000Z","formattedDate":"December 30, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.965,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Ivan Krivosheev","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-12-30-whats-new-in-seaquery-0.28.0","title":"What\'s new in SeaQuery 0.28.0","author":"SeaQL Team","author_title":"Ivan Krivosheev","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Call for Contributors and Reviewers \ud83d\udce2","permalink":"/blog/2023-01-01-call-for-contributors-n-reviewers"},"nextItem":{"title":"What\'s new in Seaography 0.3.0","permalink":"/blog/2022-12-02-whats-new-in-seaography-0.3.0"}},"content":"\ud83c\udf89 We are pleased to release SeaQuery [`0.28.0`](https://github.com/SeaQL/sea-query/releases/tag/0.28.0)! Here are some feature highlights \ud83c\udf1f:\\n\\n## New `IdenStatic` trait for static identifier\\n\\n[[#508](https://github.com/SeaQL/sea-query/pull/508)] Representing a identifier with `&\'static str`. The `IdenStatic` trait looks like this:\\n\\n```rust\\npub trait IdenStatic: Iden + Copy + \'static {\\n fn as_str(&self) -> &\'static str;\\n}\\n```\\n\\nYou can derive it easily for your existing `Iden`. Just changing the `#[derive(Iden)]` into `#[derive(IdenStatic)]`.\\n\\n```rust\\n#[derive(IdenStatic)]\\nenum User {\\n Table,\\n Id,\\n FirstName,\\n LastName,\\n #[iden = \\"_email\\"]\\n Email,\\n}\\n\\nassert_eq!(User::Email.as_str(), \\"_email\\");\\n```\\n\\n## New `PgExpr` and `SqliteExpr` traits for backend specific expressions\\n\\n[[#519](https://github.com/SeaQL/sea-query/pull/519)] Postgres specific and SQLite specific expressions are being moved into its corresponding trait. You need to import the trait into scope before construct the expression with those backend specific methods.\\n\\n```rust\\n// Importing `PgExpr` trait before constructing Postgres expression\\nuse sea_query::{extension::postgres::PgExpr, tests_cfg::*, *};\\n\\nlet query = Query::select()\\n .columns([Font::Name, Font::Variant, Font::Language])\\n .from(Font::Table)\\n .and_where(Expr::val(\\"a\\").concatenate(\\"b\\").concat(\\"c\\").concat(\\"d\\"))\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"SELECT \\"name\\", \\"variant\\", \\"language\\" FROM \\"font\\" WHERE \'a\' || \'b\' || \'c\' || \'d\'\\"#\\n);\\n```\\n\\n```rust\\n// Importing `SqliteExpr` trait before constructing SQLite expression\\n use sea_query::{extension::sqlite::SqliteExpr, tests_cfg::*, *};\\n\\n let query = Query::select()\\n .column(Font::Name)\\n .from(Font::Table)\\n .and_where(Expr::col(Font::Name).matches(\\"a\\"))\\n .to_owned();\\n\\n assert_eq!(\\n query.to_string(SqliteQueryBuilder),\\n r#\\"SELECT \\"name\\" FROM \\"font\\" WHERE \\"name\\" MATCH \'a\'\\"#\\n );\\n```\\n\\n## Bug Fixes\\n\\n* Wrap unions into parenthesis https://github.com/SeaQL/sea-query/pull/498\\n* Syntax error on empty condition https://github.com/SeaQL/sea-query/pull/505\\n```rust\\n// given\\nlet (statement, values) = sea_query::Query::select()\\n .column(Glyph::Id)\\n .from(Glyph::Table)\\n .cond_where(Cond::any()\\n .add(Cond::all()) // empty all() => TRUE\\n .add(Cond::any()) // empty any() => FALSE\\n )\\n .build(sea_query::MysqlQueryBuilder);\\n\\n// old behavior\\nassert_eq!(statement, r#\\"SELECT `id` FROM `glyph`\\"#);\\n\\n// new behavior\\nassert_eq!(\\n statement,\\n r#\\"SELECT `id` FROM `glyph` WHERE (TRUE) OR (FALSE)\\"#\\n);\\n\\n// a complex example\\nlet (statement, values) = Query::select()\\n .column(Glyph::Id)\\n .from(Glyph::Table)\\n .cond_where(\\n Cond::all()\\n .add(Cond::all().not())\\n .add(Cond::any().not())\\n .not(),\\n )\\n .build(MysqlQueryBuilder);\\n\\nassert_eq!(\\n statement,\\n r#\\"SELECT `id` FROM `glyph` WHERE NOT ((NOT TRUE) AND (NOT FALSE))\\"#\\n);\\n```\\n\\n## Breaking Changes\\n\\n* [[#535](https://github.com/SeaQL/sea-query/pull/535)] MSRV is up to 1.62\\n```shell\\n# Make sure you\'re running SeaQuery with Rust 1.62+ \ud83e\udd80\\n$ rustup update\\n```\\n\\n* [[#492](https://github.com/SeaQL/sea-query/pull/492)] `ColumnType::Array` definition changed from `Array(SeaRc>)` to `Array(SeaRc)`\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] `Func::*` now returns `FunctionCall` instead of `SimpleExpr`\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] `Func::coalesce` now accepts `IntoIterator` instead of `IntoIterator`\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] Removed `Expr::arg` and `Expr::args` - these functions are no longer needed\\n* [[#507](https://github.com/SeaQL/sea-query/pull/507)] Moved all Postgres specific operators to `PgBinOper`\\n* [[#476](https://github.com/SeaQL/sea-query/pull/476)] `Expr` methods used to accepts `Into` now accepts `Into`\\n* [[#476](https://github.com/SeaQL/sea-query/pull/476)] `Expr::is_in`, `Expr::is_not_in` now accepts `Into` instead of `Into` and convert it to `SimpleExpr::Tuple` instead of `SimpleExpr::Values`\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] `Expr::expr` now accepts `Into` instead of `SimpleExpr`\\n* [[#519](https://github.com/SeaQL/sea-query/pull/519)] Moved Postgres specific `Expr` methods to new trait `PgExpr`\\n* [[#528](https://github.com/SeaQL/sea-query/pull/528)] `Expr::equals` now accepts `C: IntoColumnRef` instead of `T: IntoIden, C: IntoIden`\\n```diff\\nuse sea_query::{*, tests_cfg::*};\\n\\nlet query = Query::select()\\n .columns([Char::Character, Char::SizeW, Char::SizeH])\\n .from(Char::Table)\\n .and_where(\\n Expr::col((Char::Table, Char::FontId))\\n- .equals(Font::Table, Font::Id)\\n+ .equals((Font::Table, Font::Id))\\n )\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(MysqlQueryBuilder),\\n r#\\"SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `character`.`font_id` = `font`.`id`\\"#\\n);\\n```\\n\\n* [[#525](https://github.com/SeaQL/sea-query/pull/525)] Removed integer and date time column types\' display length / precision option\\n\\n## API Additions\\n\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] Added `SelectStatement::from_function`\\n```rust\\nuse sea_query::{tests_cfg::*, *};\\n\\nlet query = Query::select()\\n .column(ColumnRef::Asterisk)\\n .from_function(Func::random(), Alias::new(\\"func\\"))\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(MysqlQueryBuilder),\\n r#\\"SELECT * FROM RAND() AS `func`\\"#\\n);\\n```\\n\\n* [[#486](https://github.com/SeaQL/sea-query/pull/486)] Added binary operators from the Postgres `pg_trgm` extension\\n```rust\\nuse sea_query::extension::postgres::PgBinOper;\\n\\nassert_eq!(\\n Query::select()\\n .expr(Expr::col(Font::Name).binary(PgBinOper::WordSimilarity, Expr::value(\\"serif\\")))\\n .from(Font::Table)\\n .to_string(PostgresQueryBuilder),\\n r#\\"SELECT \\"name\\" <% \'serif\' FROM \\"font\\"\\"#\\n);\\n```\\n\\n* [[#473](https://github.com/SeaQL/sea-query/pull/473)] Added `ILIKE` and `NOT ILIKE` operators\\n* [[#510](https://github.com/SeaQL/sea-query/pull/510)] Added the `mul` and `div` methods for `SimpleExpr`\\n* [[#513](https://github.com/SeaQL/sea-query/pull/513)] Added the `MATCH`, `->` and `->>` operators for SQLite\\n```rust\\nuse sea_query::extension::sqlite::SqliteBinOper;\\n\\nassert_eq!(\\n Query::select()\\n .column(Char::Character)\\n .from(Char::Table)\\n .and_where(Expr::col(Char::Character).binary(SqliteBinOper::Match, Expr::val(\\"test\\")))\\n .build(SqliteQueryBuilder),\\n (\\n r#\\"SELECT \\"character\\" FROM \\"character\\" WHERE \\"character\\" MATCH ?\\"#.to_owned(),\\n Values(vec![\\"test\\".into()])\\n )\\n);\\n```\\n\\n* [[#497](https://github.com/SeaQL/sea-query/pull/497)] Added the `FULL OUTER JOIN`\\n* [[#530](https://github.com/SeaQL/sea-query/pull/530)] Added `PgFunc::get_random_uuid`\\n* [[#528](https://github.com/SeaQL/sea-query/pull/528)] Added `SimpleExpr::eq`, `SimpleExpr::ne`, `Expr::not_equals`\\n* [[#529](https://github.com/SeaQL/sea-query/pull/529)] Added `PgFunc::starts_with`\\n* [[#535](https://github.com/SeaQL/sea-query/pull/535)] Added `Expr::custom_keyword` and `SimpleExpr::not`\\n```rust\\nuse sea_query::*;\\n\\nlet query = Query::select()\\n .expr(Expr::custom_keyword(Alias::new(\\"test\\")))\\n .to_owned();\\n\\nassert_eq!(query.to_string(MysqlQueryBuilder), r#\\"SELECT test\\"#);\\nassert_eq!(query.to_string(PostgresQueryBuilder), r#\\"SELECT test\\"#);\\nassert_eq!(query.to_string(SqliteQueryBuilder), r#\\"SELECT test\\"#);\\n```\\n\\n* [[#539](https://github.com/SeaQL/sea-query/pull/539)] Added `SimpleExpr::like`, `SimpleExpr::not_like` and `Expr::cast_as`\\n* [[#532](https://github.com/SeaQL/sea-query/pull/532)] Added support for `NULLS NOT DISTINCT` clause for Postgres\\n* [[#531](https://github.com/SeaQL/sea-query/pull/531)] Added `Expr::cust_with_expr` and `Expr::cust_with_exprs`\\n```rust\\nuse sea_query::{tests_cfg::*, *};\\n\\nlet query = Query::select()\\n .expr(Expr::cust_with_expr(\\"data @? ($1::JSONPATH)\\", \\"hello\\"))\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"SELECT data @? (\'hello\'::JSONPATH)\\"#\\n);\\n```\\n\\n* [[#538](https://github.com/SeaQL/sea-query/pull/538)] Added support for converting `&String` to Value\\n\\n## Miscellaneous Enhancements\\n\\n* [[#475](https://github.com/SeaQL/sea-query/pull/475)] New struct `FunctionCall` which hold function and arguments\\n* [[#503](https://github.com/SeaQL/sea-query/pull/503)] Support `BigDecimal`, `IpNetwork` and `MacAddress` for `sea-query-postgres`\\n* [[#511](https://github.com/SeaQL/sea-query/pull/511)] Made `value::with_array` module public and therefore making `NotU8` trait public\\n* [[#524](https://github.com/SeaQL/sea-query/pull/524)] Drop the `Sized` requirement on implementers of `SchemaBuilders`\\n\\n## Integration Examples\\n\\nSeaQuery plays well with the other crates in the rust ecosystem. \\n\\n- [Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/postgres)\\n- [Rusqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/rusqlite)\\n- [SQLx Any Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_any)\\n- [SQLx Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_postgres)\\n- [SQLx MySql Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_mysql)\\n- [SQLx Sqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_sqlite)\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future."},{"id":"2022-12-02-whats-new-in-seaography-0.3.0","metadata":{"permalink":"/blog/2022-12-02-whats-new-in-seaography-0.3.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-12-02-whats-new-in-seaography-0.3.0.md","source":"@site/blog/2022-12-02-whats-new-in-seaography-0.3.0.md","title":"What\'s new in Seaography 0.3.0","description":"\ud83c\udf89 We are pleased to release Seaography 0.3.0! Here are some feature highlights \ud83c\udf1f:","date":"2022-12-02T00:00:00.000Z","formattedDate":"December 2, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.285,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Panagiotis Karatakis","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-12-02-whats-new-in-seaography-0.3.0","title":"What\'s new in Seaography 0.3.0","author":"SeaQL Team","author_title":"Panagiotis Karatakis","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaQuery 0.28.0","permalink":"/blog/2022-12-30-whats-new-in-seaquery-0.28.0"},"nextItem":{"title":"What\'s new in SeaORM 0.10.x","permalink":"/blog/2022-11-10-whats-new-in-0.10.x"}},"content":"\ud83c\udf89 We are pleased to release Seaography [`0.3.0`](https://github.com/SeaQL/seaography/releases/tag/0.3.0)! Here are some feature highlights \ud83c\udf1f:\\n\\n## Dependency Upgrade\\n\\n[[#93](https://github.com/SeaQL/seaography/pull/93)] We have upgraded a major dependency:\\n- Upgrade [`sea-orm`](https://github.com/SeaQL/sea-orm) to 0.10\\n\\nYou might need to upgrade the corresponding dependency in your application as well.\\n\\n## Support Self Referencing Relation\\n\\n[[#99](https://github.com/SeaQL/seaography/pull/99)] You can now query self referencing models and the inverse of it.\\n\\nSelf referencing relation should be added to the `Relation` enum, note that the `belongs_to` attribute must be `belongs_to = \\"Entity\\"`.\\n\\n```rust\\nuse sea_orm::entity::prelude::*;\\n\\n#[derive(\\n Clone, Debug, PartialEq, DeriveEntityModel,\\n async_graphql::SimpleObject, seaography::macros::Filter,\\n)]\\n#[sea_orm(table_name = \\"staff\\")]\\n#[graphql(complex)]\\n#[graphql(name = \\"Staff\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub staff_id: i32,\\n pub first_name: String,\\n pub last_name: String,\\n pub reports_to_id: Option,\\n}\\n\\n#[derive(\\n Copy, Clone, Debug, EnumIter, DeriveRelation,\\n seaography::macros::RelationsCompact\\n)]\\npub enum Relation {\\n #[sea_orm(\\n belongs_to = \\"Entity\\",\\n from = \\"Column::ReportsToId\\",\\n to = \\"Column::StaffId\\",\\n )]\\n SelfRef,\\n}\\n\\nimpl ActiveModelBehavior for ActiveModel {}\\n```\\n\\nThen, you can query the related models in GraphQL.\\n\\n```graphql\\n{\\n staff {\\n nodes {\\n firstName\\n reportsToId\\n selfRefReverse {\\n staffId\\n firstName\\n }\\n selfRef {\\n staffId\\n firstName\\n }\\n }\\n }\\n}\\n```\\n\\nThe resulting JSON\\n\\n```json\\n{\\n \\"staff\\": {\\n \\"nodes\\": [\\n {\\n \\"firstName\\": \\"Mike\\",\\n \\"reportsToId\\": null,\\n \\"selfRefReverse\\": [\\n {\\n \\"staffId\\": 2,\\n \\"firstName\\": \\"Jon\\"\\n }\\n ],\\n \\"selfRef\\": null\\n },\\n {\\n \\"firstName\\": \\"Jon\\",\\n \\"reportsToId\\": 1,\\n \\"selfRefReverse\\": null,\\n \\"selfRef\\": {\\n \\"staffId\\": 1,\\n \\"firstName\\": \\"Mike\\"\\n }\\n }\\n ]\\n }\\n}\\n```\\n\\n## Web Framework Generator\\n\\n[[#74](https://github.com/SeaQL/seaography/pull/74)] You can generate `seaography` project with either Actix or Poem as the web server.\\n\\n### CLI Generator Option\\n\\nRun `seaography-cli` to generate `seaography` code with Actix or Poem as the web framework.\\n\\n```shell\\n# The command take three arguments, generating project with Poem web framework by default\\nseaography-cli \\n\\n# Generating project with Actix web framework\\nseaography-cli -f actix \\n\\n# MySQL\\nseaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql\\n# PostgreSQL\\nseaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres\\n# SQLite\\nseaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql\\n```\\n\\n### Actix\\n\\n```rust\\nuse async_graphql::{\\n dataloader::DataLoader,\\n http::{playground_source, GraphQLPlaygroundConfig},\\n EmptyMutation, EmptySubscription, Schema,\\n};\\nuse async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};\\nuse sea_orm::Database;\\nuse seaography_example_project::*;\\n// ...\\n\\nasync fn graphql_playground() -> Result {\\n Ok(HttpResponse::Ok()\\n .content_type(\\"text/html; charset=utf-8\\")\\n .body(\\n playground_source(GraphQLPlaygroundConfig::new(\\"http://localhost:8000\\"))\\n ))\\n}\\n\\n#[actix_web::main]\\nasync fn main() -> std::io::Result<()> {\\n // ...\\n\\n let database = Database::connect(db_url).await.unwrap();\\n let orm_dataloader: DataLoader = DataLoader::new(\\n OrmDataloader {\\n db: database.clone(),\\n },\\n tokio::spawn,\\n );\\n\\n let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)\\n .data(database)\\n .data(orm_dataloader)\\n .finish();\\n\\n let app = App::new()\\n .app_data(Data::new(schema.clone()))\\n .service(web::resource(\\"/\\").guard(guard::Post()).to(index))\\n .service(web::resource(\\"/\\").guard(guard::Get()).to(graphql_playground));\\n\\n HttpServer::new(app)\\n .bind(\\"127.0.0.1:8000\\")?\\n .run()\\n .await\\n}\\n```\\n\\n### Poem\\n\\n```rust\\nuse async_graphql::{\\n dataloader::DataLoader,\\n http::{playground_source, GraphQLPlaygroundConfig},\\n EmptyMutation, EmptySubscription, Schema,\\n};\\nuse async_graphql_poem::GraphQL;\\nuse poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};\\nuse sea_orm::Database;\\nuse seaography_example_project::*;\\n// ...\\n\\n#[handler]\\nasync fn graphql_playground() -> impl IntoResponse {\\n Html(playground_source(GraphQLPlaygroundConfig::new(\\"/\\")))\\n}\\n\\n#[tokio::main]\\nasync fn main() {\\n // ...\\n\\n let database = Database::connect(db_url).await.unwrap();\\n let orm_dataloader: DataLoader = DataLoader::new(\\n OrmDataloader { db: database.clone() },\\n tokio::spawn,\\n );\\n\\n let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)\\n .data(database)\\n .data(orm_dataloader)\\n .finish();\\n\\n let app = Route::new()\\n .at(\\"/\\", get(graphql_playground)\\n .post(GraphQL::new(schema)));\\n\\n Server::new(TcpListener::bind(\\"0.0.0.0:8000\\"))\\n .run(app)\\n .await\\n .unwrap();\\n}\\n```\\n\\n## Related Query Enhancement\\n\\n[[#84](https://github.com/SeaQL/seaography/pull/84)] Filtering, sorting and paginating related 1-to-many queries. Note that the pagination is work-in-progress, currently it is in memory pagination.\\n\\nFor example, find all inactive customers, include their address, and their payments with amount greater than 7 ordered by amount the second result. You can execute the query below at our [GraphQL playground](https://playground.sea-ql.org/seaography).\\n\\n```graphql\\n{\\n customer(\\n filters: { active: { eq: 0 } }\\n pagination: { cursor: { limit: 3, cursor: \\"Int[3]:271\\" } }\\n ) {\\n nodes {\\n customerId\\n lastName\\n email\\n address {\\n address\\n }\\n payment(\\n filters: { amount: { gt: \\"7\\" } }\\n orderBy: { amount: ASC }\\n pagination: { pages: { limit: 1, page: 1 } }\\n ) {\\n nodes {\\n paymentId\\n amount\\n }\\n pages\\n current\\n pageInfo {\\n hasPreviousPage\\n hasNextPage\\n }\\n }\\n }\\n pageInfo {\\n hasPreviousPage\\n hasNextPage\\n endCursor\\n }\\n }\\n}\\n```\\n\\n## Integration Examples\\n\\nWe have the following examples for you, alongside with the SQL scripts to initialize the database.\\n\\n* [MySQL](https://github.com/SeaQL/seaography/tree/main/examples/mysql)\\n* [PostgreSQL](https://github.com/SeaQL/seaography/tree/main/examples/postgres)\\n* [SQLite](https://github.com/SeaQL/seaography/tree/main/examples/sqlite)\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future."},{"id":"2022-11-10-whats-new-in-0.10.x","metadata":{"permalink":"/blog/2022-11-10-whats-new-in-0.10.x","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-11-10-whats-new-in-0.10.x.md","source":"@site/blog/2022-11-10-whats-new-in-0.10.x.md","title":"What\'s new in SeaORM 0.10.x","description":"\ud83c\udf89 We are pleased to release SeaORM 0.10.0!","date":"2022-11-10T00:00:00.000Z","formattedDate":"November 10, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":6.7,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-11-10-whats-new-in-0.10.x","title":"What\'s new in SeaORM 0.10.x","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in Seaography 0.3.0","permalink":"/blog/2022-12-02-whats-new-in-seaography-0.3.0"},"nextItem":{"title":"Toggle Stacked Download Graph in crates.io","permalink":"/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.10.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.10.0)!\\n\\n## Rust 1.65\\n\\nThe long-anticipated Rust [1.65](https://blog.rust-lang.org/2022/11/03/Rust-1.65.0.html) has been released! Generic associated types (GATs) must be the hottest newly-stabilized feature.\\n\\nHow is GAT useful to SeaORM? Let\'s take a look at the following:\\n\\n```rust\\ntrait StreamTrait<\'a>: Send + Sync {\\n type Stream: Stream> + Send;\\n\\n fn stream(\\n &\'a self,\\n stmt: Statement,\\n ) -> Pin> + \'a + Send>>;\\n}\\n```\\n\\nYou can see that the `Future` has a lifetime `\'a`, but as a side effect the lifetime is tied to `StreamTrait`.\\n\\nWith GAT, the lifetime can be elided:\\n\\n```rust\\ntrait StreamTrait: Send + Sync {\\n type Stream<\'a>: Stream> + Send\\n where\\n Self: \'a;\\n\\n fn stream<\'a>(\\n &\'a self,\\n stmt: Statement,\\n ) -> Pin, DbErr>> + \'a + Send>>;\\n}\\n```\\n\\nWhat benefit does it bring in practice? Consider you have a function that accepts a generic `ConnectionTrait` and calls `stream()`: \\n\\n```rust\\nasync fn processor<\'a, C>(conn: &\'a C) -> Result<...>\\nwhere C: ConnectionTrait + StreamTrait<\'a> {...}\\n```\\n\\nThe fact that the lifetime of the connection is tied to the stream can create confusion to the compiler, most likely when you are making transactions:\\n\\n```rust\\nasync fn do_transaction(conn: &C) -> Result<...>\\nwhere C: ConnectionTrait + TransactionTrait\\n{\\n let txn = conn.begin().await?;\\n processor(&txn).await?;\\n txn.commit().await?;\\n}\\n```\\n\\nBut now, with the lifetime of the stream elided, it\'s much easier to work on streams inside transactions because the two lifetimes are now distinct and the stream\'s lifetime is implicit:\\n\\n```rust\\nasync fn processor(conn: &C) -> Result<...>\\nwhere C: ConnectionTrait + StreamTrait {...}\\n```\\n\\nBig thanks to [@nappa85](https://github.com/nappa85) for the [contribution](https://github.com/SeaQL/sea-orm/pull/1161).\\n\\n ---\\n\\nBelow are some feature highlights \ud83c\udf1f:\\n\\n## Support Array Data Types in Postgres\\n\\n[[#1132](https://github.com/SeaQL/sea-orm/pull/1132)] Support model field of type `Vec`. (by [@hf29h8sh321](https://github.com/hf29h8sh321), [@ikrivosheev](https://github.com/ikrivosheev), [@tyt2y3](https://github.com/tyt2y3), [@billy1624](https://github.com/billy1624))\\n\\nYou can define a vector of types that are already supported by SeaORM in the model.\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"collection\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub integers: Vec,\\n pub integers_opt: Option>,\\n pub floats: Vec,\\n pub doubles: Vec,\\n pub strings: Vec,\\n}\\n```\\n\\nKeep in mind that you need to enable the `postgres-array` feature and this is a Postgres only feature.\\n\\n```toml\\nsea-orm = { version = \\"0.10\\", features = [\\"postgres-array\\", ...] }\\n```\\n\\n## Better Error Types\\n\\n[[#750](https://github.com/SeaQL/sea-orm/pull/750), [#1002](https://github.com/SeaQL/sea-orm/pull/1002)] Error types with parsable database specific error. (by [@mohs8421](https://github.com/mohs8421), [@tyt2y3](https://github.com/tyt2y3))\\n\\n```rust\\nlet mud_cake = cake::ActiveModel {\\n id: Set(1),\\n name: Set(\\"Moldy Cake\\".to_owned()),\\n price: Set(dec!(10.25)),\\n gluten_free: Set(false),\\n serial: Set(Uuid::new_v4()),\\n bakery_id: Set(None),\\n};\\n\\n// Insert a new cake with its primary key (`id` column) set to 1.\\nlet cake = mud_cake.save(db).await.expect(\\"could not insert cake\\");\\n\\n// Insert the same row again and it failed\\n// because primary key of each row should be unique.\\nlet error: DbErr = cake\\n .into_active_model()\\n .insert(db)\\n .await\\n .expect_err(\\"inserting should fail due to duplicate primary key\\");\\n\\nmatch error {\\n DbErr::Exec(RuntimeErr::SqlxError(error)) => match error {\\n Error::Database(e) => {\\n // We check the error code thrown by the database (MySQL in this case),\\n // `23000` means `ER_DUP_KEY`: we have a duplicate key in the table.\\n assert_eq!(e.code().unwrap(), \\"23000\\");\\n }\\n _ => panic!(\\"Unexpected sqlx-error kind\\"),\\n },\\n _ => panic!(\\"Unexpected Error kind\\"),\\n}\\n```\\n\\n## Run Migration on Any Postgres Schema\\n\\n[[#1056](https://github.com/SeaQL/sea-orm/pull/1056)] By default migration will be run on the `public` schema, you can now override it when running migration on the CLI or programmatically. (by [@MattGson](https://github.com/MattGson), [@nahuakang](https://github.com/nahuakang), [@billy1624](https://github.com/billy1624))\\n\\nFor CLI, you can specify the target schema with `-s` / `--database_schema` option:\\n* via sea-orm-cli: `sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema`\\n* via SeaORM migrator: `cargo run -- -u postgres://root:root@localhost/database -s my_schema`\\n\\nYou can also run the migration on the target schema programmatically:\\n\\n```rust\\nlet connect_options = ConnectOptions::new(\\"postgres://root:root@localhost/database\\".into())\\n .set_schema_search_path(\\"my_schema\\".into()) // Override the default schema\\n .to_owned();\\n\\nlet db = Database::connect(connect_options).await?\\n\\nmigration::Migrator::up(&db, None).await?;\\n```\\n\\n## Breaking Changes\\n\\n* [[#789](https://github.com/SeaQL/sea-orm/pull/789)] Replaced `usize` with `u64` in `PaginatorTrait` (by [@liberwang1013](https://github.com/liberwang1013))\\n* [[#1002](https://github.com/SeaQL/sea-orm/pull/1002)] Type signature of `DbErr` changed as a result of the PR (by [@mohs8421](https://github.com/mohs8421), [@tyt2y3](https://github.com/tyt2y3))\\n\\n* `ColumnType::Enum` structure changed:\\n```rust\\nenum ColumnType {\\n // then\\n Enum(String, Vec)\\n\\n // now\\n Enum {\\n /// Name of enum\\n name: DynIden,\\n /// Variants of enum\\n variants: Vec,\\n }\\n ...\\n}\\n```\\n\\n* A new method `array_type` was added to `ValueType`:\\n```rust\\nimpl sea_orm::sea_query::ValueType for MyType {\\n fn array_type() -> sea_orm::sea_query::ArrayType {\\n sea_orm::sea_query::ArrayType::TypeName\\n }\\n ...\\n}\\n```\\n\\n* `ActiveEnum::name()` changed return type to `DynIden`:\\n```rust\\n#[derive(Debug, Iden)]\\n#[iden = \\"category\\"]\\npub struct CategoryEnum;\\n\\nimpl ActiveEnum for Category {\\n // then\\n fn name() -> String {\\n \\"category\\".to_owned()\\n }\\n\\n // now\\n fn name() -> DynIden {\\n SeaRc::new(CategoryEnum)\\n }\\n ...\\n}\\n```\\n\\n## SeaORM Enhancements\\n\\n* [[#995](https://github.com/SeaQL/sea-orm/pull/995)] Support `time` crate for SQLite (by [@billy1624](https://github.com/billy1624))\\n* [[#902](https://github.com/SeaQL/sea-orm/pull/902)] Support `distinct` & `distinct_on` expression (by [@edg](https://github.com/edg)-l, [@kyoto7250](https://github.com/kyoto7250))\\n* [[#973](https://github.com/SeaQL/sea-orm/pull/973)] `fn column()` also handle enum type (by [@tyt2y3](https://github.com/tyt2y3), [@billy1624](https://github.com/billy1624))\\n* [[#897](https://github.com/SeaQL/sea-orm/pull/897)] Added `acquire_timeout` on `ConnectOptions` (by [@001wwang](https://github.com/001wwang), [@billy1624](https://github.com/billy1624))\\n* [[#1020](https://github.com/SeaQL/sea-orm/pull/1020)] Better compile error for entity without primary key (by [@Sytten](https://github.com/Sytten), [@billy1624](https://github.com/billy1624))\\n* [[#833](https://github.com/SeaQL/sea-orm/pull/833)] Added blanket implementations of `IntoActiveValue` for `Option` values (by [@wdcocq](https://github.com/wdcocq))\\n* [[#1112](https://github.com/SeaQL/sea-orm/pull/1112)] Added `into_model` & `into_json` to `Cursor` (by [@jukanntenn](https://github.com/jukanntenn), [@billy1624](https://github.com/billy1624))\\n* [[#1056](https://github.com/SeaQL/sea-orm/pull/1056)] Added `set_schema_search_path` method to `ConnectOptions` for setting schema search path of PostgreSQL connection (by [@MattGson](https://github.com/MattGson), [@billy1624](https://github.com/billy1624))\\n* [[#1042](https://github.com/SeaQL/sea-orm/pull/1042)] Serialize `time` types as `serde_json::Value` (by [@jimmycuadra](https://github.com/jimmycuadra), [@billy1624](https://github.com/billy1624))\\n* [[#986](https://github.com/SeaQL/sea-orm/pull/986)] Implements `fmt::Display` for `ActiveEnum` (by [@jesseduffield](https://github.com/jesseduffield), [@billy264](https://github.com/billy264))\\n* [[#990](https://github.com/SeaQL/sea-orm/pull/990)] Implements `TryFrom` for `Model` (by [@MattGson](https://github.com/MattGson), [@greenhandatsjtu](https://github.com/greenhandatsjtu))\\n\\n## CLI Enhancements\\n\\n* [[#1052](https://github.com/SeaQL/sea-orm/pull/1052)] Escape module name defined with Rust keywords (by [@andy128k](https://github.com/andy128k))\\n* [[#879](https://github.com/SeaQL/sea-orm/pull/879), [#1155](https://github.com/SeaQL/sea-orm/pull/1155)] Check to make sure migration name doesn\'t contain hyphen `-` in it (by [@AngelOnFira](https://github.com/AngelOnFira))\\n* [[#953](https://github.com/SeaQL/sea-orm/pull/953)] Generate entity files as a library or module (by [@viktorbahr](https://github.com/viktorbahr), [@HigherOrderLogic](https://github.com/HigherOrderLogic))\\n* [[#947](https://github.com/SeaQL/sea-orm/pull/947)] Generate a new migration template with name prefix of unix timestamp (by [@Animeshz](https://github.com/Animeshz))\\n* [[#933](https://github.com/SeaQL/sea-orm/pull/933)] Generate migration in modules (by [@remlse](https://github.com/remlse))\\n* [[#1019](https://github.com/SeaQL/sea-orm/pull/1019)] Generate `DeriveRelation` on empty `Relation` enum (by [@alper](https://github.com/alper), [@billy1624](https://github.com/billy1624))\\n* [[#988](https://github.com/SeaQL/sea-orm/pull/988)] Generate entity derive `Eq` if possible (by [@billy1624](https://github.com/billy1624), [@w93163red](https://github.com/w93163red))\\n* [[#1056](https://github.com/SeaQL/sea-orm/pull/1056)] Run migration on any PostgreSQL schema (by [@MattGson](https://github.com/MattGson), [@billy1624](https://github.com/billy1624))\\n* [[#864](https://github.com/SeaQL/sea-orm/pull/864), [#991](https://github.com/SeaQL/sea-orm/pull/991)] `migrate fresh` command will drop all PostgreSQL types (by [@MaderNoob](https://github.com/MaderNoob), [@karpa4o4](https://github.com/karpa4o4))\\n\\nPlease check [here](https://github.com/SeaQL/sea-orm/blob/master/CHANGELOG.md#0101---2022-10-27) for the complete changelog.\\n\\n## Integration Examples\\n\\nSeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples [wanted](https://github.com/SeaQL/sea-orm/issues/269)!\\n\\n- [Actix v4 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)\\n- [Actix v3 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example)\\n- [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)\\n- [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example)\\n- [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example)\\n- [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)\\n- [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)\\n- [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example)\\n- [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example)\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Henrik Giesel\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Jacob Trueb\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marcus Buffett\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.11.x`](https://github.com/SeaQL/sea-orm/milestone/11)."},{"id":"2022-11-09-toggle-stacked-download-graph-in-crates-io","metadata":{"permalink":"/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io.md","source":"@site/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io.md","title":"Toggle Stacked Download Graph in crates.io","description":"Not long ago we opened a PR \\"Toggle stacked download graph #5010\\" resolving Convert download chart from stacked chart to regular chart #3876 for crates.io.","date":"2022-11-09T00:00:00.000Z","formattedDate":"November 9, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":1.17,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Billy Chan","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-11-09-toggle-stacked-download-graph-in-crates-io","title":"Toggle Stacked Download Graph in crates.io","author":"SeaQL Team","author_title":"Billy Chan","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.10.x","permalink":"/blog/2022-11-10-whats-new-in-0.10.x"},"nextItem":{"title":"What\'s new in SeaQuery 0.27.0","permalink":"/blog/2022-10-31-whats-new-in-seaquery-0.27.0"}},"content":"Not long ago we opened a PR \\"[Toggle stacked download graph #5010](https://github.com/rust-lang/crates.io/pull/5010)\\" resolving [Convert download chart from stacked chart to regular chart #3876](https://github.com/rust-lang/crates.io/issues/3876) for [crates.io](https://github.com/rust-lang/crates.io).\\n\\nWhat\'s it all about?\\n\\n## Problem\\n\\nThe download graph on crates.io used to be a stacked graph. With download count of older versions stack on top of newer versions. You might misinterpret the numbers. Consider this, at the first glance, it seems that version 0.9.2 has 1,500+ downloads on Nov 7. But in fact, it has only 237 downloads that day because the graph is showing the cumulative downloads.\\n\\n![crates.io Stacked Download Graph](https://user-images.githubusercontent.com/30400950/200738670-4266e178-7952-4e05-bff0-c2445ba345bf.png)\\n\\nThis makes it hard to compare the download trend of different versions over time. Why this is important? You may ask. It\'s important to observe the adoption rate of newer version upon release. This paints a general picture if existing users are upgrading to newer version or not.\\n\\n## Solution\\n\\nThe idea is simple but effective: having a dropdown to toggle between stacked and unstacked download graph. With this, one can switch between both display mode, comparing the download trend of different version and observing the most download version in the past 90 days are straightforward and intuitive.\\n\\n![crates.io Unstacked Download Graph](https://user-images.githubusercontent.com/30400950/200741006-6a5e1922-de38-456b-b33d-dfc4ce2f8a93.png)\\n\\n## Conclusion\\n\\nThis is a great tool for us to gauge the adoption rate of our new releases and we highly encourage user upgrading to newer release that contains feature updates and bug fixes."},{"id":"2022-10-31-whats-new-in-seaquery-0.27.0","metadata":{"permalink":"/blog/2022-10-31-whats-new-in-seaquery-0.27.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-10-31-whats-new-in-seaquery-0.27.0.md","source":"@site/blog/2022-10-31-whats-new-in-seaquery-0.27.0.md","title":"What\'s new in SeaQuery 0.27.0","description":"\ud83c\udf89 We are pleased to release SeaQuery 0.27.0! Here are some feature highlights \ud83c\udf1f:","date":"2022-10-31T00:00:00.000Z","formattedDate":"October 31, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.26,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Ivan Krivosheev","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-10-31-whats-new-in-seaquery-0.27.0","title":"What\'s new in SeaQuery 0.27.0","author":"SeaQL Team","author_title":"Ivan Krivosheev","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Toggle Stacked Download Graph in crates.io","permalink":"/blog/2022-11-09-toggle-stacked-download-graph-in-crates-io"},"nextItem":{"title":"Getting Started with Seaography","permalink":"/blog/2022-09-27-getting-started-with-seaography"}},"content":"\ud83c\udf89 We are pleased to release SeaQuery [`0.27.0`](https://github.com/SeaQL/sea-query/releases/tag/0.27.0)! Here are some feature highlights \ud83c\udf1f:\\n\\n## Dependency Upgrade\\n\\n[[#356](https://github.com/SeaQL/sea-query/issues/356)] We have upgraded a major dependency:\\n- Upgrade [`sqlx`](https://github.com/launchbadge/sqlx) to 0.6.1\\n\\nYou might need to upgrade the corresponding dependency in your application as well.\\n\\n## Drivers support\\n\\nWe have reworked the way drivers work in SeaQuery: priori to `0.27.0`, users have to invoke the `sea_query_driver_*` macros. Now each driver `sqlx`, `postgres` & `rusqlite` has their own supporting crate, which integrates tightly with the corresponding libraries. Checkout our integration examples below for more details.\\n\\n[[#383](https://github.com/SeaQL/sea-query/issues/383)] Deprecate `sea-query-driver` in favour of `sea-query-binder`\\n\\n[[#422](https://github.com/SeaQL/sea-query/pull/422)] Rusqlite support is moved to `sea-query-rusqlite`\\n\\n[[#433](https://github.com/SeaQL/sea-query/pull/433)] Postgres support is moved to `sea-query-postgres`\\n\\n```rust\\n// before\\nsea_query::sea_query_driver_postgres!();\\nuse sea_query_driver_postgres::{bind_query, bind_query_as};\\n\\nlet (sql, values) = Query::select()\\n .from(Character::Table)\\n .expr(Func::count(Expr::col(Character::Id)))\\n .build(PostgresQueryBuilder);\\n\\nlet row = bind_query(sqlx::query(&sql), &values)\\n .fetch_one(&mut pool)\\n .await\\n .unwrap();\\n\\n// now\\nuse sea_query_binder::SqlxBinder;\\n\\nlet (sql, values) = Query::select()\\n .from(Character::Table)\\n .expr(Func::count(Expr::col(Character::Id)))\\n .build_sqlx(PostgresQueryBuilder);\\n\\nlet row = sqlx::query_with(&sql, values)\\n .fetch_one(&mut pool)\\n .await\\n .unwrap();\\n\\n// You can now make use of SQLx\'s `query_as_with` nicely:\\nlet rows = sqlx::query_as_with::<_, StructWithFromRow, _>(&sql, values)\\n .fetch_all(&mut pool)\\n .await\\n .unwrap();\\n```\\n\\n## Support sub-query operators: `EXISTS`, `ALL`, `ANY`, `SOME`\\n\\n[[#118](https://github.com/SeaQL/sea-query/issues/118)] Added sub-query operators: `EXISTS`, `ALL`, `ANY`, `SOME`\\n\\n```rust\\nlet query = Query::select()\\n .column(Char::Id)\\n .from(Char::Table)\\n .and_where(\\n Expr::col(Char::Id)\\n .eq(\\n Expr::any(\\n Query::select().column(Char::Id).from(Char::Table).take()\\n )\\n )\\n )\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(MysqlQueryBuilder),\\n r#\\"SELECT `id` FROM `character` WHERE `id` = ANY(SELECT `id` FROM `character`)\\"#\\n);\\nassert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"SELECT \\"id\\" FROM \\"character\\" WHERE \\"id\\" = ANY(SELECT \\"id\\" FROM \\"character\\")\\"#\\n);\\n```\\n\\n## Support `ON CONFLICT WHERE`\\n\\n[[#366](https://github.com/SeaQL/sea-query/issues/366)] Added support to `ON CONFLICT WHERE`\\n\\n```rust\\nlet query = Query::insert()\\n .into_table(Glyph::Table)\\n .columns([Glyph::Aspect, Glyph::Image])\\n .values_panic(vec![\\n 2.into(),\\n 3.into(),\\n ])\\n .on_conflict(\\n OnConflict::column(Glyph::Id)\\n .update_expr((Glyph::Image, Expr::val(1).add(2)))\\n .target_and_where(Expr::tbl(Glyph::Table, Glyph::Aspect).is_null())\\n .to_owned()\\n )\\n .to_owned();\\n\\nassert_eq!(\\n query.to_string(MysqlQueryBuilder),\\n r#\\"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `image` = 1 + 2\\"#\\n);\\nassert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"INSERT INTO \\"glyph\\" (\\"aspect\\", \\"image\\") VALUES (2, 3) ON CONFLICT (\\"id\\") WHERE \\"glyph\\".\\"aspect\\" IS NULL DO UPDATE SET \\"image\\" = 1 + 2\\"#\\n);\\nassert_eq!(\\n query.to_string(SqliteQueryBuilder),\\n r#\\"INSERT INTO \\"glyph\\" (\\"aspect\\", \\"image\\") VALUES (2, 3) ON CONFLICT (\\"id\\") WHERE \\"glyph\\".\\"aspect\\" IS NULL DO UPDATE SET \\"image\\" = 1 + 2\\"#\\n);\\n```\\n\\n## Changed cond_where chaining semantics\\n\\n[[#414](https://github.com/SeaQL/sea-query/issues/414)] Changed cond_where chaining semantics\\n\\n```rust\\n// Before: will extend current Condition\\nassert_eq!(\\n Query::select()\\n .cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])\\n .cond_where(Expr::col(Glyph::Id).eq(3))\\n .to_owned()\\n .to_string(PostgresQueryBuilder),\\n r#\\"SELECT WHERE \\"id\\" = 1 OR \\"id\\" = 2 OR \\"id\\" = 3\\"#\\n);\\n// Before: confusing, since it depends on the order of invocation:\\nassert_eq!(\\n Query::select()\\n .cond_where(Expr::col(Glyph::Id).eq(3))\\n .cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])\\n .to_owned()\\n .to_string(PostgresQueryBuilder),\\n r#\\"SELECT WHERE \\"id\\" = 3 AND (\\"id\\" = 1 OR \\"id\\" = 2)\\"#\\n);\\n// Now: will always conjoin with `AND`\\nassert_eq!(\\n Query::select()\\n .cond_where(Expr::col(Glyph::Id).eq(1))\\n .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])\\n .to_owned()\\n .to_string(PostgresQueryBuilder),\\n r#\\"SELECT WHERE \\"id\\" = 1 AND (\\"id\\" = 2 OR \\"id\\" = 3)\\"#\\n);\\n// Now: so they are now equivalent\\nassert_eq!(\\n Query::select()\\n .cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])\\n .cond_where(Expr::col(Glyph::Id).eq(1))\\n .to_owned()\\n .to_string(PostgresQueryBuilder),\\n r#\\"SELECT WHERE (\\"id\\" = 2 OR \\"id\\" = 3) AND \\"id\\" = 1\\"#\\n);\\n```\\n\\n## Added `OnConflict::value` and `OnConflict::values`\\n\\n[[#451](https://github.com/SeaQL/sea-query/issues/451)] Implementation `From` for any `Into` into `SimpleExpr`\\n\\n```rust\\n// Before: notice the tuple\\nOnConflict::column(Glyph::Id).update_expr((Glyph::Image, Expr::val(1).add(2)))\\n// After: it accepts `Value` as well as `SimpleExpr`\\nOnConflict::column(Glyph::Id).value(Glyph::Image, Expr::val(1).add(2))\\n```\\n\\n## Improvement to `ColumnDef::default`\\n\\n[[#347](https://github.com/SeaQL/sea-query/issues/347)] `ColumnDef::default` now accepts `Into` instead `Into`\\n\\n```rust\\n// Now we can write:\\nColumnDef::new(Char::FontId)\\n .timestamp()\\n .default(Expr::current_timestamp())\\n```\\n\\n\\n## Breaking Changes\\n\\n- [[#386](https://github.com/SeaQL/sea-query/pull/386)] Changed `in_tuples` interface to accept `IntoValueTuple`\\n- [[#320](https://github.com/SeaQL/sea-query/issues/320)] Removed deprecated methods\\n- [[#440](https://github.com/SeaQL/sea-query/issues/440)] `CURRENT_TIMESTAMP` changed from being a function to keyword\\n- [[#375](https://github.com/SeaQL/sea-query/issues/375)] Update SQLite `boolean` type from `integer to `boolean`\\n- [[#451](https://github.com/SeaQL/sea-query/issues/451)] Deprecated `OnConflict::update_value`, `OnConflict::update_values`, `OnConflict::update_expr`, `OnConflict::update_exprs`\\n- [[#451](https://github.com/SeaQL/sea-query/issues/451)] Deprecated `InsertStatement::exprs`, `InsertStatement::exprs_panic`\\n- [[#451](https://github.com/SeaQL/sea-query/issues/451)] Deprecated `UpdateStatement::col_expr`, `UpdateStatement::value_expr`, `UpdateStatement::exprs`\\n- [[#451](https://github.com/SeaQL/sea-query/issues/451)] `UpdateStatement::value` now accept `Into` instead of `Into`\\n- [[#451](https://github.com/SeaQL/sea-query/issues/451)] `Expr::case`, `CaseStatement::case` and `CaseStatement::finally` now accepts `Into` instead of `Into`\\n- [[#460](https://github.com/SeaQL/sea-query/pull/460)] `InsertStatement::values`, `UpdateStatement::values` now accepts `IntoIterator` instead of `IntoIterator`\\n- [[#409](https://github.com/SeaQL/sea-query/issues/409)] Use native api from SQLx for SQLite to work with time\\n- [[#435](https://github.com/SeaQL/sea-query/pull/435)] Changed type of `ColumnType::Enum` from `(String, Vec)` to `Enum { name: DynIden, variants: Vec}`\\n\\n## Miscellaneous Enhancements\\n\\n- [[#336](https://github.com/SeaQL/sea-query/issues/336)] Added support one dimension Postgres array for SQLx\\n- [[#373](https://github.com/SeaQL/sea-query/issues/373)] Support CROSS JOIN\\n- [[#457](https://github.com/SeaQL/sea-query/issues/457)] Added support `DROP COLUMN` for SQLite\\n- [[#466](https://github.com/SeaQL/sea-query/pull/466)] Added `YEAR`, `BIT` and `VARBIT` types\\n- [[#338](https://github.com/SeaQL/sea-query/issues/338)] Handle Postgres schema name for schema statements\\n- [[#418](https://github.com/SeaQL/sea-query/issues/418)] Added `%`, `<<` and `>>` binary operators\\n- [[#329](https://github.com/SeaQL/sea-query/pull/430)] Added RAND function\\n- [[#425](https://github.com/SeaQL/sea-query/pull/425)] Implements `Display` for `Value`\\n- [[#427](https://github.com/SeaQL/sea-query/issues/427)] Added `INTERSECT` and `EXCEPT` to UnionType\\n- [[#448](https://github.com/SeaQL/sea-query/pull/448)] `OrderedStatement::order_by_customs`, `OrderedStatement::order_by_columns`, `OverStatement::partition_by_customs`, `OverStatement::partition_by_columns` now accepts `IntoIterator` instead of `Vec`\\n- [[#452](https://github.com/SeaQL/sea-query/issues/452)] `TableAlterStatement::rename_column`, `TableAlterStatement::drop_column`, `ColumnDef::new`, `ColumnDef::new_with_type` now accepts `IntoIden` instead of `Iden`\\n- [[#426](https://github.com/SeaQL/sea-query/pull/426)] Cleanup `IndexBuilder` trait methods\\n- [[#436](https://github.com/SeaQL/sea-query/pull/436)] Introduce `SqlWriter` trait\\n- [[#448](https://github.com/SeaQL/sea-query/pull/448)] Remove unneeded `vec!` from examples\\n\\n## Bug Fixes\\n\\n- [[#449](https://github.com/SeaQL/sea-query/issues/449)] `distinct_on` properly handles `ColumnRef`\\n- [[#461](https://github.com/SeaQL/sea-query/issues/461)] Removed `ON` for `DROP INDEX` for SQLite\\n- [[#468](https://github.com/SeaQL/sea-query/pull/468)] Change datetime string format to include microseconds\\n- [[#452](https://github.com/SeaQL/sea-query/issues/452)] `ALTER TABLE` for PosgreSQL with `UNIQUE` constraint\\n\\n## Integration Examples\\n\\nSeaQuery plays well with the other crates in the rust ecosystem. \\n\\n- [Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/postgres)\\n- [Rusqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/rusqlite)\\n- [SQLx Any Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_any)\\n- [SQLx Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_postgres)\\n- [SQLx MySql Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_mysql)\\n- [SQLx Sqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_sqlite)\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future."},{"id":"2022-09-27-getting-started-with-seaography","metadata":{"permalink":"/blog/2022-09-27-getting-started-with-seaography","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-09-27-getting-started-with-seaography.md","source":"@site/blog/2022-09-27-getting-started-with-seaography.md","title":"Getting Started with Seaography","description":"Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.","date":"2022-09-27T00:00:00.000Z","formattedDate":"September 27, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":5.315,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-09-27-getting-started-with-seaography","title":"Getting Started with Seaography","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaQuery 0.27.0","permalink":"/blog/2022-10-31-whats-new-in-seaquery-0.27.0"},"nextItem":{"title":"Introducing Seaography \ud83e\udded","permalink":"/blog/2022-09-17-introducing-seaography"}},"content":"[Seaography](https://github.com/SeaQL/seaography) is a GraphQL framework for building GraphQL resolvers using [SeaORM](https://github.com/SeaQL/sea-orm). It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.\\n\\nThe design and implementation of Seaography can be found on our [release blog post](https://www.sea-ql.org/blog/2022-09-17-introducing-seaography/) and [documentation](https://www.sea-ql.org/Seaography/).\\n\\n## Extending a SeaORM project\\n\\nSince Seaography is built on top of SeaORM, you can easily build a GraphQL server from a SeaORM project.\\n\\nStart by adding Seaography and GraphQL dependencies to your `Cargo.toml`.\\n\\n```diff title=Cargo.toml\\n[dependencies]\\nsea-orm = { version = \\"^0.9\\", features = [ ... ] }\\n+ seaography = { version = \\"^0.1\\", features = [ \\"with-decimal\\", \\"with-chrono\\" ] }\\n+ async-graphql = { version = \\"4.0.10\\", features = [\\"decimal\\", \\"chrono\\", \\"dataloader\\"] }\\n+ async-graphql-poem = { version = \\"4.0.10\\" }\\n```\\n\\nThen, derive a few macros on the SeaORM entities.\\n\\n```diff title=src/entities/film_actor.rs\\nuse sea_orm::entity::prelude::*;\\n\\n#[derive(\\n Clone,\\n Debug,\\n PartialEq,\\n DeriveEntityModel,\\n+ async_graphql::SimpleObject,\\n+ seaography::macros::Filter,\\n)]\\n+ #[graphql(complex)]\\n+ #[graphql(name = \\"FilmActor\\")]\\n#[sea_orm(table_name = \\"film_actor\\")]\\npub struct Model {\\n #[sea_orm(primary_key, auto_increment = false)]\\n pub actor_id: i32,\\n #[sea_orm(primary_key, auto_increment = false)]\\n pub film_id: i32,\\n pub last_update: DateTimeUtc,\\n}\\n\\n#[derive(\\n Copy,\\n Clone,\\n Debug,\\n EnumIter,\\n DeriveRelation,\\n+ seaography::macros::RelationsCompact,\\n)]\\npub enum Relation {\\n #[sea_orm(\\n belongs_to = \\"super::film::Entity\\",\\n from = \\"Column::FilmId\\",\\n to = \\"super::film::Column::FilmId\\",\\n on_update = \\"Cascade\\",\\n on_delete = \\"NoAction\\"\\n )]\\n Film,\\n #[sea_orm(\\n belongs_to = \\"super::actor::Entity\\",\\n from = \\"Column::ActorId\\",\\n to = \\"super::actor::Column::ActorId\\",\\n on_update = \\"Cascade\\",\\n on_delete = \\"NoAction\\"\\n )]\\n Actor,\\n}\\n```\\n\\nWe also need to define `QueryRoot` for the GraphQL server. This define the GraphQL schema.\\n\\n```rust title=src/query_root.rs\\n#[derive(Debug, seaography::macros::QueryRoot)]\\n#[seaography(entity = \\"crate::entities::actor\\")]\\n#[seaography(entity = \\"crate::entities::film\\")]\\n#[seaography(entity = \\"crate::entities::film_actor\\")]\\npub struct QueryRoot;\\n```\\n\\n```rust title=src/lib.rs\\nuse sea_orm::prelude::*;\\n\\npub mod entities;\\npub mod query_root;\\n\\npub use query_root::QueryRoot;\\n\\npub struct OrmDataloader {\\n pub db: DatabaseConnection,\\n}\\n```\\n\\nFinally, create an executable to drive the GraphQL server.\\n\\n``` rust title=src/main.rs\\nuse async_graphql::{\\n dataloader::DataLoader,\\n http::{playground_source, GraphQLPlaygroundConfig},\\n EmptyMutation, EmptySubscription, Schema,\\n};\\nuse async_graphql_poem::GraphQL;\\nuse poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};\\nuse sea_orm::Database;\\nuse seaography_example_project::*;\\n// ...\\n\\n#[handler]\\nasync fn graphql_playground() -> impl IntoResponse {\\n Html(playground_source(GraphQLPlaygroundConfig::new(\\"/\\")))\\n}\\n\\n#[tokio::main]\\nasync fn main() {\\n // ...\\n\\n let database = Database::connect(db_url).await.unwrap();\\n let orm_dataloader: DataLoader = DataLoader::new(\\n OrmDataloader { db: database.clone() },\\n tokio::spawn,\\n );\\n\\n let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)\\n .data(database)\\n .data(orm_dataloader)\\n .finish();\\n\\n let app = Route::new()\\n .at(\\"/\\", get(graphql_playground)\\n .post(GraphQL::new(schema)));\\n\\n Server::new(TcpListener::bind(\\"0.0.0.0:8000\\"))\\n .run(app)\\n .await\\n .unwrap();\\n}\\n```\\n\\n## Generating a project from database\\n\\nIf all you have is a database schema, good news! You can setup a GraphQL server without writing a single line of code.\\n\\nInstall `seaography-cli`, it helps you generate SeaORM entities along with a full Rust project based on a database schema.\\n\\n```shell\\ncargo install seaography-cli\\n```\\n\\nRun `seaography-cli` to generate code for the GraphQL server.\\n\\n```shell\\n# The command take three arguments\\nseaography-cli \\n\\n# MySQL\\nseaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql\\n# PostgreSQL\\nseaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres\\n# SQLite\\nseaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql\\n```\\n\\n## Checkout the example projects\\n\\nWe have the following examples for you, alongside with the SQL scripts to initialize the database.\\n\\n* [MySQL](https://github.com/SeaQL/seaography/tree/main/examples/mysql)\\n* [PostgreSQL](https://github.com/SeaQL/seaography/tree/main/examples/postgres)\\n* [SQLite](https://github.com/SeaQL/seaography/tree/main/examples/sqlite)\\n\\nAll examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a [demo GraphQL playground](https://playground.sea-ql.org/seaography) in case you can\'t wait to play with it.\\n\\n## Starting the GraphQL Server\\n\\nYour GraphQL server is ready to launch! Go to the Rust project root then execute `cargo run` to spin it up.\\n\\n```shell\\n$ cargo run\\n\\nPlayground: http://localhost:8000\\n```\\n\\nVisit the GraphQL playground at [http://localhost:8000](http://localhost:8000)\\n\\n![GraphQL Playground](https://www.sea-ql.org/Seaography/img/playground_example_database.png)\\n\\n## Query Data via GraphQL\\n\\nLet say we want to get the first 3 films released on or after year 2006 sorted in ascending order of its title.\\n\\n```graphql\\n{\\n film(\\n pagination: { limit: 3, page: 0 }\\n filters: { releaseYear: { gte: \\"2006\\" } }\\n orderBy: { title: ASC }\\n ) {\\n data {\\n filmId\\n title\\n description\\n releaseYear\\n filmActor {\\n actor {\\n actorId\\n firstName\\n lastName\\n }\\n }\\n }\\n pages\\n current\\n }\\n}\\n```\\n\\nWe got the following JSON result after running the GraphQL query.\\n\\n```json\\n{\\n \\"data\\": {\\n \\"film\\": {\\n \\"data\\": [\\n {\\n \\"filmId\\": 1,\\n \\"title\\": \\"ACADEMY DINOSAUR\\",\\n \\"description\\": \\"An Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies\\",\\n \\"releaseYear\\": \\"2006\\",\\n \\"filmActor\\": [\\n {\\n \\"actor\\": {\\n \\"actorId\\": 1,\\n \\"firstName\\": \\"PENELOPE\\",\\n \\"lastName\\": \\"GUINESS\\"\\n }\\n },\\n {\\n \\"actor\\": {\\n \\"actorId\\": 10,\\n \\"firstName\\": \\"CHRISTIAN\\",\\n \\"lastName\\": \\"GABLE\\"\\n }\\n },\\n // ...\\n ]\\n },\\n {\\n \\"filmId\\": 2,\\n \\"title\\": \\"ACE GOLDFINGER\\",\\n \\"description\\": \\"A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China\\",\\n \\"releaseYear\\": \\"2006\\",\\n \\"filmActor\\": [\\n // ...\\n ]\\n },\\n // ...\\n ],\\n \\"pages\\": 334,\\n \\"current\\": 0\\n }\\n }\\n}\\n```\\n\\nBehind the scene, the following SQL were queried:\\n\\n```sql\\nSELECT \\"film\\".\\"film_id\\",\\n \\"film\\".\\"title\\",\\n \\"film\\".\\"description\\",\\n \\"film\\".\\"release_year\\",\\n \\"film\\".\\"language_id\\",\\n \\"film\\".\\"original_language_id\\",\\n \\"film\\".\\"rental_duration\\",\\n \\"film\\".\\"rental_rate\\",\\n \\"film\\".\\"length\\",\\n \\"film\\".\\"replacement_cost\\",\\n \\"film\\".\\"rating\\",\\n \\"film\\".\\"special_features\\",\\n \\"film\\".\\"last_update\\"\\nFROM \\"film\\"\\nWHERE \\"film\\".\\"release_year\\" >= \'2006\'\\nORDER BY \\"film\\".\\"title\\" ASC\\nLIMIT 3 OFFSET 0\\n\\nSELECT \\"film_actor\\".\\"actor_id\\", \\"film_actor\\".\\"film_id\\", \\"film_actor\\".\\"last_update\\"\\nFROM \\"film_actor\\"\\nWHERE \\"film_actor\\".\\"film_id\\" IN (1, 3, 2)\\n\\nSELECT \\"actor\\".\\"actor_id\\", \\"actor\\".\\"first_name\\", \\"actor\\".\\"last_name\\", \\"actor\\".\\"last_update\\"\\nFROM \\"actor\\"\\nWHERE \\"actor\\".\\"actor_id\\" IN (24, 162, 20, 160, 1, 188, 123, 30, 53, 40, 2, 64, 85, 198, 10, 19, 108, 90)\\n```\\n\\nUnder the hood, Seaography uses [async_graphql::dataloader](https://docs.rs/async-graphql/latest/async_graphql/dataloader/index.html) in querying nested objects to tackle the N+1 problem.\\n\\nTo learn more, checkout the [Seaography Documentation](https://www.sea-ql.org/Seaography/docs/data-loader/).\\n\\n## Conclusion\\n\\nSeaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.\\n\\nHowever, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.\\n\\n## People\\n\\nSeaography is created by:\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Panagiotis Karatakis\\n
\\n Summer of Code Contributor; developer of Seaography\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n Summer of Code Mentor; lead developer of SeaQL\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n Summer of Code Mentor; core member of SeaQL\\n
\\n
\\n
\\n
\\n
"},{"id":"2022-09-17-introducing-seaography","metadata":{"permalink":"/blog/2022-09-17-introducing-seaography","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-09-17-introducing-seaography.md","source":"@site/blog/2022-09-17-introducing-seaography.md","title":"Introducing Seaography \ud83e\udded","description":"What a fruitful Summer of Code! Today, we are excited to introduce Seaography to the SeaQL community. Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.","date":"2022-09-17T00:00:00.000Z","formattedDate":"September 17, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.525,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-09-17-introducing-seaography","title":"Introducing Seaography \ud83e\udded","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Getting Started with Seaography","permalink":"/blog/2022-09-27-getting-started-with-seaography"},"nextItem":{"title":"Celebrating 3,000+ GitHub Stars \ud83c\udf89","permalink":"/blog/2022-08-12-3k-github-stars"}},"content":"What a fruitful Summer of Code! Today, we are excited to introduce [Seaography](https://github.com/SeaQL/seaography) to the SeaQL community. Seaography is a GraphQL framework for building GraphQL resolvers using [SeaORM](https://github.com/SeaQL/sea-orm). It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.\\n\\n## Motivation\\n\\nWe observed that other ecosystems have similar tools such as PostGraphile and Hasura allowing users to query a database via GraphQL with minimal effort upfront. We decided to bring that seamless experience to the Rust ecosystem.\\n\\nFor existing SeaORM users, adding a GraphQL API is straight forward. Start by adding `seaography` and `async-graphql` dependencies to your crate. Then, deriving a few extra derive macros to the SeaORM entities. Finally, spin up a GraphQL server to serve queries!\\n\\nIf you are new to SeaORM, no worries, we have your back. You only need to provide a database connection, and `seaography-cli` will generate the SeaORM entities together with a complete Rust project!\\n\\n## Design\\n\\nWe considered two approaches in our [initial discussion](https://github.com/SeaQL/summer-of-code/discussions/12): 1) blackbox query engine 2) code generator. The drawback with a blackbox query engine is it\'s difficult to customize or extend its behaviour, making it difficult to develop and operate in the long run. We opted the code generator approach, giving users full control and endless possibilities with the versatile async Rust ecosystem.\\n\\nThis project is separated into the following crates:\\n\\n* [`seaography`](https://github.com/SeaQL/seaography): The facade crate; exporting macros, structures and helper functions to turn SeaORM entities into GraphQL nodes.\\n\\n* [`seaography-cli`](https://github.com/SeaQL/seaography/tree/main/cli): The CLI tool; it generates SeaORM entities along with a full Rust project based on a user-provided database.\\n\\n* [`seaography-discoverer`](https://github.com/SeaQL/seaography/tree/main/discoverer): A helper crate used by the CLI tool to discover the database schema and transform into a generic format.\\n\\n* [`seaography-generator`](https://github.com/SeaQL/seaography/tree/main/generator): A helper crate used by the CLI tool to consume the database schema and generate a full Rust project.\\n\\n* [`seaography-derive`](https://github.com/SeaQL/seaography/tree/main/derive): A set of procedural macros to derive types and trait implementations on SeaORM entities, turning them into GraphQL nodes.\\n\\n## Features\\n\\n* Relational query (1-to-1, 1-to-N)\\n* Pagination on query\'s root entity\\n* Filter with operators (e.g. gt, lt, eq)\\n* Order by any column\\n\\n## Getting Started\\n\\nTo quick start, we have the following examples for you, alongside with the SQL scripts to initialize the database.\\n\\n* [MySQL](https://github.com/SeaQL/seaography/tree/main/examples/mysql)\\n* [PostgreSQL](https://github.com/SeaQL/seaography/tree/main/examples/postgres)\\n* [SQLite](https://github.com/SeaQL/seaography/tree/main/examples/sqlite)\\n\\nAll examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a [demo GraphQL playground](https://playground.sea-ql.org/seaography) in case you can\'t wait to play with it.\\n\\nFor more documentation, visit [www.sea-ql.org/Seaography](https://www.sea-ql.org/Seaography/).\\n\\n## What\'s Next?\\n\\nThis project passed the first milestone shipping the essential features, but it still has a long way to go. The next milestone would be:\\n\\n* Query enhancements\\n * Filter related queries\\n * Filter based on related queries properties\\n * Paginate related queries\\n * Order by related queries\\n* Cursor based pagination\\n* Single entity query\\n* Mutations\\n * Insert single entity\\n * Insert batch entities\\n * Update single entity\\n * Update batch entities using filter\\n * Delete single entity\\n * Delete batch entities\\n\\n## Conclusion\\n\\nSeaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.\\n\\nHowever, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.\\n\\n## People\\n\\nSeaography is created by:\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Panagiotis Karatakis\\n
\\n Summer of Code Contributor; developer of Seaography\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n Summer of Code Mentor; lead developer of SeaQL\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n Summer of Code Mentor; core member of SeaQL\\n
\\n
\\n
\\n
\\n
"},{"id":"2022-08-12-3k-github-stars","metadata":{"permalink":"/blog/2022-08-12-3k-github-stars","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-08-12-3k-github-stars.md","source":"@site/blog/2022-08-12-3k-github-stars.md","title":"Celebrating 3,000+ GitHub Stars \ud83c\udf89","description":"We are celebrating the milestone of reaching 3,000 GitHub stars across all SeaQL repositories!","date":"2022-08-12T00:00:00.000Z","formattedDate":"August 12, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":5.61,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-08-12-3k-github-stars","title":"Celebrating 3,000+ GitHub Stars \ud83c\udf89","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Introducing Seaography \ud83e\udded","permalink":"/blog/2022-09-17-introducing-seaography"},"nextItem":{"title":"What\'s new in SeaQuery 0.26.0","permalink":"/blog/2022-08-05-whats-new-in-seaquery-0.26.0"}},"content":"We are celebrating the milestone of reaching 3,000 GitHub stars across all SeaQL repositories!\\n\\nThis wouldn\'t have happened without your support and contribution, so we want to thank the community for being with us along the way.\\n\\n## The Journey\\n\\nSeaQL.org was founded back in 2020. We devoted ourselves into developing open source libraries that help Rust developers to build data intensive applications. In the past two years, we published and maintained four open source libraries: [SeaQuery](https://github.com/SeaQL/sea-query), [SeaSchema](https://github.com/SeaQL/sea-schema), [SeaORM](https://github.com/SeaQL/sea-orm) and [StarfishQL](https://github.com/SeaQL/starfish-ql). Each library is designed to fill a niche in the Rust ecosystem, and they are made to play well with other Rust libraries.\\n\\n### 2020\\n\\n- Oct 2020: **SeaQL founded**\\n- Dec 2020: **SeaQuery first released**\\n\\n### 2021\\n\\n- Apr 2021: **SeaSchema first released**\\n- Aug 2021: **SeaORM first released**\\n- Nov 2021: SeaORM reached 0.4.0\\n- Dec 2021: SeaQuery reached 0.20.0\\n- Dec 2021: SeaSchema reached 0.4.0\\n\\n### 2022\\n\\n- Apr 2022: SeaQL selected as a Google Summer of Code 2022 [mentor organization](https://summerofcode.withgoogle.com/programs/2022/organizations/seaql)\\n- Apr 2022: **StarfishQL first released**\\n- Jul 2022: SeaQuery reached 0.26.2\\n- Jul 2022: SeaSchema reached 0.9.3\\n- Jul 2022: SeaORM reached 0.9.1\\n- Aug 2022: **SeaQL reached 3,000+ GitHub stars**\\n\\n## Where\'re We Now?\\n\\nWe\'re pleased by the adoption by the Rust community. We couldn\'t make it this far without your feedback and contributions.\\n\\n
\\n
\\n
\\n
\\n 4 \ud83d\udce6\\n
\\n
\\n Open source projects\\n
\\n
\\n
\\n
\\n
\\n
\\n 5 \ud83c\udfec\\n
\\n
\\n Startups using SeaQL\\n
\\n
\\n
\\n
\\n
\\n
\\n 1,972 \ud83c\udf88\\n
\\n
\\n Dependent projects\\n
\\n
\\n
\\n
\\n
\\n
\\n 131 \ud83d\udc68\u200d\ud83d\udc69\u200d\ud83d\udc67\u200d\ud83d\udc66\\n
\\n
\\n Contributors\\n
\\n
\\n
\\n
\\n
\\n
\\n 1,061 \u2705\\n
\\n
\\n Merged PRs & resolved issues\\n
\\n
\\n
\\n
\\n
\\n
\\n 3,158 \u2b50\\n
\\n
\\n GitHub stars\\n
\\n
\\n
\\n
\\n
\\n
\\n 432 \ud83d\udde3\ufe0f\\n
\\n
\\n Discord members\\n
\\n
\\n
\\n
\\n
\\n
\\n 87,937 \u2328\ufe0f\\n
\\n
\\n Lines of Rust\\n
\\n
\\n
\\n
\\n
\\n
\\n 667,769 \ud83d\udcbf\\n
\\n
\\n Downloads on crates.io\\n
\\n
\\n
\\n
\\n\\n
\\n

\\n

* as of Aug 12

\\n
\\n\\n## Core Members\\n\\nOur team has grown from two people initially into four. We always welcome passionate engineers to join us!\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n Founder. Led the initial development and maintaining the projects.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n Founding member. Contributed many features and bug fixes. Keeps the community alive.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ivan Krivosheev\\n
\\n Joined in 2022. Contributed many features and bug fixes, most notably to SeaQuery.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sanford Pun\\n
\\n Developed StarfishQL and wrote SeaORM\'s tutorial.\\n
\\n
\\n
\\n
\\n\\n## Special Thanks\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n Contributed transaction, streaming and tracing API to SeaORM.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n nitnelave\\n
\\n Contributed binder crate and other improvements to SeaQuery.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sam Samai\\n
\\n Developed SeaORM\'s test suite and demo schema.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Daniel Lyne\\n
\\n Developed SeaSchema\'s Postgres implementation.\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Charles Chege\\n
\\n Developed SeaSchema\'s SQLite implementation.\\n
\\n
\\n
\\n
\\n\\n## Sponsors\\n\\nIf you are feeling generous, a small [donation](https://github.com/sponsors/SeaQL) will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Contributors\\n\\nMany features and enhancements are actually proposed and implemented by the community. We want to take this chance to thank all our contributors!\\n\\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n \\n \\n \\n
\\n
\\n\\n## What\'s Next?\\n\\nWe have two ongoing [Summer of Code 2022 projects](https://www.sea-ql.org/blog/2022-06-02-summer-of-code-2022-intro/) to enrich the SeaQL ecosystem, planning to be released later this year. In the meantime, we\'re focusing on improving existing SeaQL libraries until reaching version 1.0, we\'d love to hear comments and feedback from the community.\\n\\nIf you like what we do, consider starring, commenting, sharing, contributing and together building for Rust\'s future!"},{"id":"2022-08-05-whats-new-in-seaquery-0.26.0","metadata":{"permalink":"/blog/2022-08-05-whats-new-in-seaquery-0.26.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-08-05-whats-new-in-seaquery-0.26.0.md","source":"@site/blog/2022-08-05-whats-new-in-seaquery-0.26.0.md","title":"What\'s new in SeaQuery 0.26.0","description":"\ud83c\udf89 We are pleased to release SeaQuery 0.26.0! Here are some feature highlights \ud83c\udf1f:","date":"2022-08-05T00:00:00.000Z","formattedDate":"August 5, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":2.535,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Ivan Krivosheev","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-08-05-whats-new-in-seaquery-0.26.0","title":"What\'s new in SeaQuery 0.26.0","author":"SeaQL Team","author_title":"Ivan Krivosheev","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Celebrating 3,000+ GitHub Stars \ud83c\udf89","permalink":"/blog/2022-08-12-3k-github-stars"},"nextItem":{"title":"Engineering at SeaQL.org","permalink":"/blog/2022-07-30-engineering"}},"content":"\ud83c\udf89 We are pleased to release SeaQuery [`0.26.0`](https://github.com/SeaQL/sea-query/releases/tag/0.26.0)! Here are some feature highlights \ud83c\udf1f:\\n\\n## Dependency Upgrades\\n\\n[[#356](https://github.com/SeaQL/sea-query/issues/356)] We have upgraded a few major dependencies:\\n- Upgrade [`sqlx`](https://github.com/launchbadge/sqlx) to 0.6\\n- Upgrade [`time`](https://github.com/time-rs/time) to 0.3\\n- Upgrade [`uuid`](https://github.com/uuid-rs/uuid) to 1.0\\n- Upgrade [`bigdecimal`](https://github.com/akubera/bigdecimal-rs) to 0.3\\n- Upgrade [`ipnetwork`](https://github.com/achanda/ipnetwork) to 0.19\\n\\nNote that you might need to upgrade the corresponding dependency on your application as well.\\n\\n## VALUES lists\\n\\n[[#351](https://github.com/SeaQL/sea-query/issues/350)] Add support for VALUES lists\\n\\n```rust\\n// SELECT * FROM (VALUES (1, \'hello\'), (2, \'world\')) AS \\"x\\"\\nlet query = SelectStatement::new()\\n .expr(Expr::asterisk())\\n .from_values(vec![(1i32, \\"hello\\"), (2, \\"world\\")], Alias::new(\\"x\\"))\\n .to_owned();\\n\\n assert_eq!(\\n query.to_string(PostgresQueryBuilder), \\n r#\\"SELECT * FROM (VALUES (1, \'hello\'), (2, \'world\')) AS \\"x\\"\\"#\\n );\\n```\\n\\n## Introduce sea-query-binder\\n\\n[[#273](https://github.com/SeaQL/sea-query/issues/273)] Native support SQLx without marcos\\n\\n```rust\\nuse sea_query_binder::SqlxBinder;\\n\\n// Create SeaQuery query with prepare SQLx\\nlet (sql, values) = Query::select()\\n .columns([\\n Character::Id,\\n Character::Uuid,\\n Character::Character,\\n Character::FontSize,\\n Character::Meta,\\n Character::Decimal,\\n Character::BigDecimal,\\n Character::Created,\\n Character::Inet,\\n Character::MacAddress,\\n ])\\n .from(Character::Table)\\n .order_by(Character::Id, Order::Desc)\\n .build_sqlx(PostgresQueryBuilder);\\n\\n// Execute query\\nlet rows = sqlx::query_as_with::<_, CharacterStructChrono, _>(&sql, values)\\n .fetch_all(&mut pool)\\n .await?;\\n\\n// Print rows\\nfor row in rows.iter() {\\n println!(\\"{:?}\\", row);\\n}\\n```\\n\\n## CASE WHEN statement support \\n\\n[[#304](https://github.com/SeaQL/sea-query/pull/304)] Add support for `CASE WHEN` statement\\n\\n```rust\\nlet query = Query::select()\\n .expr_as(\\n CaseStatement::new()\\n .case(Expr::tbl(Glyph::Table, Glyph::Aspect).is_in(vec![2, 4]), Expr::val(true))\\n .finally(Expr::val(false)),\\n Alias::new(\\"is_even\\")\\n )\\n .from(Glyph::Table)\\n .to_owned();\\n \\nassert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"SELECT (CASE WHEN (\\"glyph\\".\\"aspect\\" IN (2, 4)) THEN TRUE ELSE FALSE END) AS \\"is_even\\" FROM \\"glyph\\"\\"#\\n);\\n```\\n\\n## Add support for Ip(4,6)Network and MacAddress\\n\\n[[#309](https://github.com/SeaQL/sea-query/pull/309)] Add support for Network types in PostgreSQL backend\\n\\n## Introduce sea-query-attr\\n\\n[[#296](https://github.com/SeaQL/sea-query/issues/296)] Proc-macro for deriving Iden enum from struct\\n\\n```rust\\nuse sea_query::gen_type_def;\\n\\n#[gen_type_def]\\npub struct Hello {\\n pub name: String\\n}\\n\\nprintln!(\\"{:?}\\", HelloTypeDef::Name);\\n```\\n\\n## Add ability to alter foreign keys\\n\\n[[#299](https://github.com/SeaQL/sea-query/pull/299)] Add support for `ALTER` foreign Keys\\n\\n```rust\\nlet foreign_key_char = TableForeignKey::new()\\n .name(\\"FK_character_glyph\\")\\n .from_tbl(Char::Table)\\n .from_col(Char::FontId)\\n .from_col(Char::Id)\\n .to_tbl(Glyph::Table)\\n .to_col(Char::FontId)\\n .to_col(Char::Id)\\n .to_owned();\\n\\nlet table = Table::alter()\\n .table(Character::Table)\\n .add_foreign_key(&foreign_key_char)\\n .to_owned();\\n\\nassert_eq!(\\n table.to_string(PostgresQueryBuilder),\\n vec![\\n r#\\"ALTER TABLE \\"character\\"\\"#,\\n r#\\"ADD CONSTRAINT \\"FK_character_glyph\\"\\"#,\\n r#\\"FOREIGN KEY (\\"font_id\\", \\"id\\") REFERENCES \\"glyph\\" (\\"font_id\\", \\"id\\")\\"#,\\n r#\\"ON DELETE CASCADE ON UPDATE CASCADE,\\"#,\\n ]\\n .join(\\" \\")\\n);\\n```\\n\\n## Select DISTINCT ON\\n\\n[[#250](https://github.com/SeaQL/sea-query/issues/250)]\\n\\n```rust\\nlet query = Query::select()\\n .from(Char::Table)\\n .distinct_on(vec![Char::Character])\\n .column(Char::Character)\\n .column(Char::SizeW)\\n .column(Char::SizeH)\\n .to_owned();\\n \\n assert_eq!(\\n query.to_string(PostgresQueryBuilder),\\n r#\\"SELECT DISTINCT ON (\\"character\\") \\"character\\", \\"size_w\\", \\"size_h\\" FROM \\"character\\"\\"#\\n );\\n```\\n\\n## Miscellaneous Enhancements\\n\\n- [[#353](https://github.com/SeaQL/sea-query/pull/353)] Support `LIKE ... ESCAPE ...` expression \\n- [[#306](https://github.com/SeaQL/sea-query/pull/306)] Move `escape` and `unescape` string to backend\\n- [[#365](https://github.com/SeaQL/sea-query/pull/365)] Add method to make a column nullable\\n- [[#348](https://github.com/SeaQL/sea-query/pull/348)] Add `is` & `is_not` to Expr\\n- [[#349](https://github.com/SeaQL/sea-query/pull/349)] Add `CURRENT_TIMESTAMP` function\\n- [[#345](https://github.com/SeaQL/sea-query/pull/345)] Add `in_tuple` method to Expr\\n- [[#266](https://github.com/SeaQL/sea-query/pull/266)] Insert Default\\n- [[#324](https://github.com/SeaQL/sea-query/pull/324)] Make `sea-query-driver` an optional dependency\\n- [[#334](https://github.com/SeaQL/sea-query/pull/334)] Add `ABS` function\\n- [[#332](https://github.com/SeaQL/sea-query/pull/332)] Support `IF NOT EXISTS` when create index\\n- [[#314](https://github.com/SeaQL/sea-query/pull/314)] Support different `blob` types in MySQL\\n- [[#331](https://github.com/SeaQL/sea-query/pull/331)] Add `VarBinary` column type\\n- [[#335](https://github.com/SeaQL/sea-query/pull/335)] `RETURNING` expression supporting `SimpleExpr`\\n\\n## Integration Examples\\n\\nSeaQuery plays well with the other crates in the rust ecosystem. \\n\\n- [Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/postgres)\\n- [Rusqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/rusqlite)\\n- [SQLx Any Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_any)\\n- [SQLx Postgres Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_postgres)\\n- [SQLx MySql Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_mysql)\\n- [SQLx Sqlite Example](https://github.com/SeaQL/sea-query/tree/master/examples/sqlx_sqlite)\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future."},{"id":"2022-07-30-engineering","metadata":{"permalink":"/blog/2022-07-30-engineering","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-07-30-engineering.md","source":"@site/blog/2022-07-30-engineering.md","title":"Engineering at SeaQL.org","description":"It\'s hard to pin down the exact date, but I think SeaQL.org was setup in July 2020, a little over a year ago. Over the course of the year, SeaORM went from 0.1 to 0.9 and the number of users kept growing. I would like to outline our engineering process in this blog post, and perhaps it can serve as a reference or guidance to prospective contributors and the future maintainer of this project.","date":"2022-07-30T00:00:00.000Z","formattedDate":"July 30, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.3,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"Lead Developer","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2022-07-30-engineering","title":"Engineering at SeaQL.org","author":"Chris Tsang","author_title":"Lead Developer","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"What\'s new in SeaQuery 0.26.0","permalink":"/blog/2022-08-05-whats-new-in-seaquery-0.26.0"},"nextItem":{"title":"What\'s new in SeaORM 0.9.0","permalink":"/blog/2022-07-17-whats-new-in-0.9.0"}},"content":"It\'s hard to pin down the exact date, but I think SeaQL.org was setup in July 2020, a little over a year ago. Over the course of the year, SeaORM went from 0.1 to 0.9 and the number of users kept growing. I would like to outline our engineering process in this blog post, and perhaps it can serve as a reference or guidance to prospective contributors and the future maintainer of this project.\\n\\nIn the open source world, the Benevolent Dictator for Life (BDL) model underpins a number of successful open source projects. That\'s not me! As a maintainer, I believe in an open, bottom-up, iterative and progressive approach. Let me explain each of these words and what they mean to me.\\n\\n### Open\\n\\nOpen as in source availability, but also engineering. We always welcome new contributors! We\'d openly discuss ideas and designs. I would often explain why a decision was made in the first place for various things. The project is structured not as a monorepo, but several interdependent repos. This reduces the friction for new contributors, because they can have a smaller field of vision to focus on solving one particular problem at hand.\\n\\n### Bottom-up\\n\\nWe rely on users to file feature requests, bug reports and of course pull requests to drive the project forward. The great thing is, for every feature / bug fix, there is a use case for it and a confirmation from a real user that it works and is reasonable. As a maintainer, I could not have first hand experience for all features and so could not understand some of the pain points.\\n\\n### Iterative\\n\\nOpen source software is imperfect, impermanent and incomplete. While I do have a grand vision in mind, we do not try rushing it all the way in one charge, nor keeping a project secret until it is \'complete\'. Good old \'release early, release often\' - we would release an initial working version of a tool, gather user feedback and improve upon it, often reimplementing a few things and break a few others - which brings us to the next point.\\n\\n### Progressive\\n\\nFavour progression. Always look forward and leave legacy behind. It does not mean that we would arbitrary break things, but when a decision is made, we\'d always imagine how the software *should be* without historic context. We\'d provide migrate paths and encourage users to move forward with us. After all, Rust is a young and evolving language! You may or may not know that `async` was just stabilized in 2020.\\n\\nEnough said for the philosophy, let\'s now talk about the actual engineering process.\\n\\n### 1. Idea & Design\\n\\nWe first have some vague idea on what problem we want to tackle. As we put in more details to the use case, we can define the problem and brainstorm solutions. Then we look for workable ways to implement that in Rust.\\n\\n### 2. Implementation\\n\\nAn initial proof of concept is appreciated. We iterate on the implementation to reduce the impact and improve the maintainability.\\n\\n### 3. Testing\\n\\nWe rely on automated tests. Every feature should come with corresponding tests, and a release is good if and only if all tests are green. Which means for features not covered by our test suite, it is an uncertainty to when we would break them. So if certain undocumented feature is important to you, we encourage you to add that to our test suite.\\n\\n### 4. Documentation\\n\\nCoding is not complete without documentation. Rust doc tests kill two birds with one stone and so is greatly appreciated. For SeaORM we have separate documentation repository and tutorial repository. It takes a lot of effort to maintain those to be up to date, and right now it\'s mostly done by our core contributors.\\n\\n### 5. Release\\n\\nWe run on a release train model, although the frequency varies. The ethos is to have small number breaking changes often. At one point, SeaQuery has a new release every week. SeaORM runs on monthly, although it more or less relaxes to bimonthly now. At any time, we maintain two branches, the latest release and master. PRs are always merged into master, and if it is non-breaking (and worthy) I would backport it to the release branch and make a minor release. At the end, I want to maintain momentum and move forward together with the community. Users can have a rough expectation on when merges will be released. And there are just **lots** of change we cannot avoid a breaking release as of the current state of the Rust ecosystem. Users are advised to upgrade regularly, and we ship along many small improvements to encourage that.\\n\\n## Conclusion\\n\\nOpen source software is a collaborative effort and thank you all who participated! Also a big thanks to SeaQL\'s core contributors who made wonders. If you have not already, I invite you to star all our repositories. If you want to support us materially, a small donation would make a big difference. SeaQL the organization is still in its infancy, and your support is vital to SeaQL\'s longevity and the prospect of the Rust community."},{"id":"2022-07-17-whats-new-in-0.9.0","metadata":{"permalink":"/blog/2022-07-17-whats-new-in-0.9.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-07-17-whats-new-in-0.9.0.md","source":"@site/blog/2022-07-17-whats-new-in-0.9.0.md","title":"What\'s new in SeaORM 0.9.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.9.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2022-07-17T00:00:00.000Z","formattedDate":"July 17, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":10.01,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-07-17-whats-new-in-0.9.0","title":"What\'s new in SeaORM 0.9.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Engineering at SeaQL.org","permalink":"/blog/2022-07-30-engineering"},"nextItem":{"title":"Welcome Summer of Code 2022 Contributors","permalink":"/blog/2022-06-02-summer-of-code-2022-intro"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.9.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.9.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Dependency Upgrades\\n\\n[[#834](https://github.com/SeaQL/sea-orm/pull/834)] We have upgraded a few major dependencies:\\n- Upgrade [`sqlx`](https://github.com/launchbadge/sqlx) to 0.6\\n- Upgrade [`time`](https://github.com/time-rs/time) to 0.3\\n- Upgrade [`uuid`](https://github.com/uuid-rs/uuid) to 1.0\\n- Upgrade [`sea-query`](https://github.com/SeaQL/sea-query) to 0.26\\n- Upgrade [`sea-schema`](https://github.com/SeaQL/sea-schema) to 0.9\\n\\nNote that you might need to upgrade the corresponding dependency on your application as well.\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Rob Gilson\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n boraarslan\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Cursor Pagination\\n\\n[[#822](https://github.com/SeaQL/sea-orm/pull/822)] Paginate models based on column(s) such as the primary key.\\n\\n```rust\\n// Create a cursor that order by `cake`.`id`\\nlet mut cursor = cake::Entity::find().cursor_by(cake::Column::Id);\\n\\n// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100\\ncursor.after(1).before(100);\\n\\n// Get first 10 rows (order by `cake`.`id` ASC)\\nlet rows: Vec = cursor.first(10).all(db).await?;\\n\\n// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order)\\nlet rows: Vec = cursor.last(10).all(db).await?;\\n```\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Lucas Berezy\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Insert On Conflict\\n\\n[[#791](https://github.com/SeaQL/sea-orm/pull/791)] Insert an active model with on conflict behaviour.\\n\\n```rust\\nlet orange = cake::ActiveModel {\\n id: ActiveValue::set(2),\\n name: ActiveValue::set(\\"Orange\\".to_owned()),\\n};\\n\\n// On conflict do nothing: \\n// - INSERT INTO \\"cake\\" (\\"id\\", \\"name\\") VALUES (2, \'Orange\') ON CONFLICT (\\"name\\") DO NOTHING\\ncake::Entity::insert(orange.clone())\\n .on_conflict(\\n sea_query::OnConflict::column(cake::Column::Name)\\n .do_nothing()\\n .to_owned()\\n )\\n .exec(db)\\n .await?;\\n\\n// On conflict do update:\\n// - INSERT INTO \\"cake\\" (\\"id\\", \\"name\\") VALUES (2, \'Orange\') ON CONFLICT (\\"name\\") DO UPDATE SET \\"name\\" = \\"excluded\\".\\"name\\"\\ncake::Entity::insert(orange)\\n .on_conflict(\\n sea_query::OnConflict::column(cake::Column::Name)\\n .update_column(cake::Column::Name)\\n .to_owned()\\n )\\n .exec(db)\\n .await?;\\n```\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n baoyachi. Aka Rust Hairy crabs\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n liberwang1013\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Join Table with Custom Conditions and Table Alias\\n\\n[[#793](https://github.com/SeaQL/sea-orm/pull/793), [#852](https://github.com/SeaQL/sea-orm/pull/852)] Click [Custom Join Conditions](https://www.sea-ql.org/SeaORM/docs/next/relation/custom-join-condition) and [Custom Joins](https://www.sea-ql.org/SeaORM/docs/next/advanced-query/custom-joins) to learn more.\\n\\n```rust\\nassert_eq!(\\n cake::Entity::find()\\n .column_as(\\n Expr::tbl(Alias::new(\\"fruit_alias\\"), fruit::Column::Name).into_simple_expr(),\\n \\"fruit_name\\"\\n )\\n .join_as(\\n JoinType::LeftJoin,\\n cake::Relation::Fruit\\n .def()\\n .on_condition(|_left, right| {\\n Expr::tbl(right, fruit::Column::Name)\\n .like(\\"%tropical%\\")\\n .into_condition()\\n }),\\n Alias::new(\\"fruit_alias\\")\\n )\\n .build(DbBackend::MySql)\\n .to_string(),\\n [\\n \\"SELECT `cake`.`id`, `cake`.`name`, `fruit_alias`.`name` AS `fruit_name` FROM `cake`\\",\\n \\"LEFT JOIN `fruit` AS `fruit_alias` ON `cake`.`id` = `fruit_alias`.`cake_id` AND `fruit_alias`.`name` LIKE \'%tropical%\'\\",\\n ]\\n .join(\\" \\")\\n);\\n```\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Tuetuopay\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Lo\xefc\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Matt\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n liberwang1013\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## (de)serialize Custom JSON Type\\n\\n[[#794](https://github.com/SeaQL/sea-orm/pull/794)] JSON stored in the database could be deserialized into custom struct in Rust.\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"json_struct\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n // JSON column defined in `serde_json::Value`\\n pub json: Json,\\n // JSON column defined in custom struct\\n pub json_value: KeyValue,\\n pub json_value_opt: Option,\\n}\\n\\n// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize`\\n#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromJsonQueryResult)]\\npub struct KeyValue {\\n pub id: i32,\\n pub name: String,\\n pub price: f32,\\n pub notes: Option,\\n}\\n```\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Mara Schulke\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Derived Migration Name\\n\\n[[#736](https://github.com/SeaQL/sea-orm/pull/736)] Introduce `DeriveMigrationName` procedural macros to infer migration name from the file name.\\n\\n```rust\\nuse sea_orm_migration::prelude::*;\\n\\n// Used to be...\\npub struct Migration;\\n\\nimpl MigrationName for Migration {\\n fn name(&self) -> &str {\\n \\"m20220120_000001_create_post_table\\"\\n }\\n}\\n\\n// Now... derive `DeriveMigrationName`,\\n// no longer have to specify the migration name explicitly\\n#[derive(DeriveMigrationName)]\\npub struct Migration;\\n\\n#[async_trait::async_trait]\\nimpl MigrationTrait for Migration {\\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n manager\\n .create_table( ... )\\n .await\\n }\\n\\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n manager\\n .drop_table( ... )\\n .await\\n }\\n}\\n```\\n\\n
\\n
\\n
\\n
\\n Proposed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n smonv\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Lukas Potthast\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## SeaORM CLI Improvements\\n\\n- [[#735](https://github.com/SeaQL/sea-orm/pull/735)] Improve logging of generate entity command\\n- [[#588](https://github.com/SeaQL/sea-orm/pull/588)] Generate enum with numeric like variants\\n- [[#755](https://github.com/SeaQL/sea-orm/pull/755)] Allow old pending migration to be applied\\n- [[#837](https://github.com/SeaQL/sea-orm/pull/837)] Skip generating entity for ignored tables\\n- [[#724](https://github.com/SeaQL/sea-orm/pull/724)] Generate code for `time` crate\\n- [[#850](https://github.com/SeaQL/sea-orm/pull/850)] Add various blob column types\\n- [[#422](https://github.com/SeaQL/sea-orm/pull/422)] Generate entity files with Postgres\'s schema name\\n- [[#851](https://github.com/SeaQL/sea-orm/pull/851)] Skip checking connection string for credentials\\n\\n
\\n
\\n Proposed & Contributed by:\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n ttys3\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n kyoto7250\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n yb3616\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Bastian\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Nahua\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Mike\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Frank Horvath\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Maikel Wever\\n
\\n
\\n
\\n
\\n
\\n\\n## Miscellaneous Enhancements\\n\\n- [[#800](https://github.com/SeaQL/sea-orm/pull/800)] Added `sqlx_logging_level` to `ConnectOptions`\\n- [[#768](https://github.com/SeaQL/sea-orm/pull/768)] Added `num_items_and_pages` to `Paginator`\\n- [[#849](https://github.com/SeaQL/sea-orm/pull/849)] Added `TryFromU64` for `time`\\n- [[#853](https://github.com/SeaQL/sea-orm/pull/853)] Include column name in `TryGetError::Null`\\n- [[#778](https://github.com/SeaQL/sea-orm/pull/778)] Refactor stream metrics\\n\\n
\\n
\\n Proposed & Contributed by:\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n SandaruKasa\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Eric\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Renato Dinhani\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n kyoto7250\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n
\\n
\\n
\\n
\\n\\n## Integration Examples\\n\\nSeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples [wanted](https://github.com/SeaQL/sea-orm/issues/269)!\\n\\n- [Actix v4 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)\\n- [Actix v3 Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix3_example)\\n- [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)\\n- [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example)\\n- [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example)\\n- [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)\\n- [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)\\n- [Salvo Example](https://github.com/SeaQL/sea-orm/tree/master/examples/salvo_example)\\n- [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example)\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.10.x`](https://github.com/SeaQL/sea-orm/milestone/10)."},{"id":"2022-06-02-summer-of-code-2022-intro","metadata":{"permalink":"/blog/2022-06-02-summer-of-code-2022-intro","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-06-02-summer-of-code-2022-intro.md","source":"@site/blog/2022-06-02-summer-of-code-2022-intro.md","title":"Welcome Summer of Code 2022 Contributors","description":"We are thrilled to announce that we will bring in four contributors this summer! Two of them are sponsored by Google while two of them are sponsored by SeaQL.","date":"2022-06-02T00:00:00.000Z","formattedDate":"June 2, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.675,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-06-02-summer-of-code-2022-intro","title":"Welcome Summer of Code 2022 Contributors","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.9.0","permalink":"/blog/2022-07-17-whats-new-in-0.9.0"},"nextItem":{"title":"What\'s new in SeaORM 0.8.0","permalink":"/blog/2022-05-15-whats-new-in-0.8.0"}},"content":"We are thrilled to announce that we will bring in four contributors this summer! Two of them are sponsored by Google while two of them are sponsored by SeaQL.\\n\\n### A GraphQL Framework on Top of SeaORM\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Panagiotis Karatakis\\n
\\n
\\n
\\n
\\n
\\n\\nI\'m Panagiotis, I live in Athens Greece and currently I pursue my second bachelors on economic sciences. My first bachelors was on computer science and I\'ve a great passion on studying and implementing enterprise software solutions. I know Rust the last year and I used it almost daily for a small startup project that me and my friends build for a startup competition.\\n\\nI\'ll be working on creating a CLI tool that will explore a database schema and then generate a ready to build `async-graphql` API. The tool will allow quick integration with the SeaQL and Rust ecosystems as well as GraphQL. To be more specific, for database exploring I\'ll use `sea-schema` and `sea-orm-codegen` for entity generation, my job is to glue those together with `async-graphql` library. You can read more [here](https://github.com/SeaQL/summer-of-code/discussions/12).\\n\\n### SQL Interpreter for Mock Testing\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Samyak Sarnayak\\n
\\n
\\n
\\n
\\n
\\n\\nI\'m Samyak Sarnayak, a final year Computer Science student from Bangalore, India. I started learning Rust around 6-7 months ago and it feels like I have found the perfect language for me :D. It does not have a runtime, has a great type system, really good compiler errors, good tooling, some functional programming patterns and metaprogramming. You can find more about me on my GitHub profile.\\n\\nI\'ll be working on a new SQL interpreter for mock testing. This will be built specifically for testing and so the emphasis will be on correctness - it can be slow but the operations must always be correct. I\'m hoping to build a working version of this and integrate it into the existing tests of SeaORM. Here is the [discussion](https://github.com/SeaQL/summer-of-code/discussions/11) for this project.\\n\\n### Support TiDB in the SeaQL Ecosystem\\n\\nEdit: This project was canceled.\\n\\n### Query Linter for SeaORM\\n\\nEdit: This project was canceled.\\n\\n## Mentors\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n\\nI am a strong believer in open source. I started my GitHub journey 10 years ago, when I published my first programming library. I had been looking for a programming language with speed, ergonomic and expressiveness. Until I found Rust.\\n\\nSeeing a niche and demand for data engineering tools in the Rust ecosystem, I founded SeaQL in 2020 and have been leading the development and maintaining the libraries since then.\\n\\n
\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\nHey, this is Billy from Hong Kong. I\'ve been using open-source libraries ever since I started coding but it\'s until 2020, I dedicated myself to be a Rust open-source developer.\\n\\nI was also a full-stack developer specialized in formulating requirement specifications for user interfaces and database structures, implementing and testing both frontend and backend from ground up, finally releasing the MVP for production and maintaining it for years to come.\\n\\nI enjoy working with Rustaceans across the globe, building a better and sustainable ecosystem for Rust community. If you like what we do, consider starring, commenting, sharing and contributing, it would be much appreciated.\\n\\n
\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sanford Pun\\n
\\n
\\n
\\n
\\n
\\n\\nI\'m Sanford, an enthusiastic software engineer who enjoys problem-solving! I\'ve worked on Rust for a couple of years now. During my early days with Rust, I focused more on the field of graphics/image processing, where I fell in love with what the language has to offer! This year, I\'ve been exploring data engineering in the StarfishQL project.\\n\\nA toast to the endless potential of Rust!\\n\\n## Community\\n\\nIf you are interested in the projects and want to share your thoughts, please star and watch the [SeaQL/summer-of-code](https://github.com/SeaQL/summer-of-code) repository on GitHub and join us on our [Discord server](https://discord.com/invite/uCPdDXzbdv)!"},{"id":"2022-05-15-whats-new-in-0.8.0","metadata":{"permalink":"/blog/2022-05-15-whats-new-in-0.8.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-05-15-whats-new-in-0.8.0.md","source":"@site/blog/2022-05-15-whats-new-in-0.8.0.md","title":"What\'s new in SeaORM 0.8.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.8.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2022-05-15T00:00:00.000Z","formattedDate":"May 15, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.775,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-05-15-whats-new-in-0.8.0","title":"What\'s new in SeaORM 0.8.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Welcome Summer of Code 2022 Contributors","permalink":"/blog/2022-06-02-summer-of-code-2022-intro"},"nextItem":{"title":"SeaORM FAQ.01","permalink":"/blog/2022-05-14-faq-01"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.8.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.8.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Migration Utilities Moved to `sea-orm-migration` crate\\n\\n[[#666](https://github.com/SeaQL/sea-orm/pull/666)] Utilities of SeaORM migration have been moved from `sea-schema` to `sea-orm-migration` crate. Users are advised to upgrade from older versions with the following steps:\\n\\n1. Bump `sea-orm` version to `0.8.0`.\\n2. Replace `sea-schema` dependency with `sea-orm-migration` in your `migration` crate.\\n ```diff title=migration/Cargo.toml\\n [dependencies]\\n - sea-schema = { version = \\"^0.7.0\\", ... }\\n + sea-orm-migration = { version = \\"^0.8.0\\" }\\n ```\\n3. Find and replace `use sea_schema::migration::` with `use sea_orm_migration::` in your `migration` crate.\\n ```diff\\n - use sea_schema::migration::prelude::*;\\n + use sea_orm_migration::prelude::*;\\n\\n - use sea_schema::migration::*;\\n + use sea_orm_migration::*;\\n ```\\n\\n
\\n
\\n Designed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Generating New Migration\\n\\n[[#656](https://github.com/SeaQL/sea-orm/pull/656)] You can create a new migration with the `migrate generate` subcommand. This simplifies the migration process, as new migrations no longer need to be added manually.\\n\\n```shell\\n# A migration file `MIGRATION_DIR/src/mYYYYMMDD_HHMMSS_create_product_table.rs` will be created.\\n# And, the migration file will be imported and included in the migrator located at `MIGRATION_DIR/src/lib.rs`.\\nsea-orm-cli migrate generate create_product_table\\n```\\n\\n
\\n
\\n Proposed & Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Viktor Bahr\\n
\\n
\\n
\\n
\\n
\\n\\n## Inserting One with Default\\n\\n[[#589](https://github.com/SeaQL/sea-orm/pull/589)] Insert a row populate with default values. Note that the target table should have default values defined for all of its columns.\\n\\n```rust\\nlet pear = fruit::ActiveModel {\\n ..Default::default() // all attributes are `NotSet`\\n};\\n\\n// The SQL statement:\\n// - MySQL: INSERT INTO `fruit` VALUES ()\\n// - SQLite: INSERT INTO \\"fruit\\" DEFAULT VALUES\\n// - PostgreSQL: INSERT INTO \\"fruit\\" VALUES (DEFAULT) RETURNING \\"id\\", \\"name\\", \\"cake_id\\"\\nlet pear: fruit::Model = pear.insert(db).await?;\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Crypto-Virus\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Checking if an `ActiveModel` is changed\\n\\n[[#683](https://github.com/SeaQL/sea-orm/pull/683)] You can check whether any field in an `ActiveModel` is `Set` with the help of the `is_changed` method.\\n\\n```rust\\nlet mut fruit: fruit::ActiveModel = Default::default();\\nassert!(!fruit.is_changed());\\n\\nfruit.set(fruit::Column::Name, \\"apple\\".into());\\nassert!(fruit.is_changed());\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Karol Fuksiewicz\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Kirawi\\n
\\n
\\n
\\n
\\n
\\n\\n## Minor Improvements\\n\\n- [[#670](https://github.com/SeaQL/sea-orm/pull/670)] Add `max_connections` option to `sea-orm-cli generate entity` subcommand\\n- [[#677](https://github.com/SeaQL/sea-orm/pull/677)] Derive `Eq` and `Clone` for `DbErr`\\n\\n
\\n
\\n Proposed & Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n benluelo\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sebastien Guillemot\\n
\\n
\\n
\\n
\\n
\\n\\n## Integration Examples\\n\\nSeaORM plays well with the other crates in the async ecosystem. It can be integrated easily with common RESTful frameworks and also gRPC frameworks; check out our new [Tonic example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example) to see how it works. More examples [wanted](https://github.com/SeaQL/sea-orm/issues/269)!\\n\\n- [Rocket Example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)\\n- [Actix Example](https://github.com/SeaQL/sea-orm/tree/master/examples/actix_example)\\n- [Axum Example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example)\\n- [Poem Example](https://github.com/SeaQL/sea-orm/tree/master/examples/poem_example)\\n- [GraphQL Example](https://github.com/SeaQL/sea-orm/tree/master/examples/graphql_example)\\n- [jsonrpsee Example](https://github.com/SeaQL/sea-orm/tree/master/examples/jsonrpsee_example)\\n- [Tonic Example](https://github.com/SeaQL/sea-orm/tree/master/examples/tonic_example)\\n\\n## Who\'s using SeaORM?\\n\\nThe following products are powered by SeaORM:\\n\\n\\n \\n \\n \\n \\n \\n \\n \\n


A lightweight web security auditing toolkit

The enterprise ready webhooks service

A personal search engine
\\n\\nSeaORM is the foundation of [StarfishQL](https://github.com/SeaQL/starfish-ql), an experimental graph database and query engine.\\n\\nFor more projects, see [Built with SeaORM](https://github.com/SeaQL/sea-orm/blob/master/COMMUNITY.md#built-with-seaorm).\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.9.x`](https://github.com/SeaQL/sea-orm/milestone/9).\\n\\n## GSoC 2022\\n\\nWe are super excited to be selected as a Google Summer of Code 2022 [mentor organization](https://summerofcode.withgoogle.com/programs/2022/organizations/seaql). The application is now closed, but the program is about to start! If you have thoughts over how we are going to implement the [project ideas](https://github.com/SeaQL/summer-of-code/tree/main/2022), feel free to participate in the discussion."},{"id":"2022-05-14-faq-01","metadata":{"permalink":"/blog/2022-05-14-faq-01","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-05-14-faq-01.md","source":"@site/blog/2022-05-14-faq-01.md","title":"SeaORM FAQ.01","description":"FAQ.01 Why SeaORM does not nest objects for parent-child relation?","date":"2022-05-14T00:00:00.000Z","formattedDate":"May 14, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":1.605,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2022-05-14-faq-01","title":"SeaORM FAQ.01","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.8.0","permalink":"/blog/2022-05-15-whats-new-in-0.8.0"},"nextItem":{"title":"Introducing StarfishQL","permalink":"/blog/2022-04-04-introducing-starfish-ql"}},"content":"## FAQ.01 Why SeaORM does not nest objects for parent-child relation?\\n\\n```rust\\nlet cake_with_fruits: Vec<(cake::Model, Vec)> =\\n Cake::find().find_with_related(Fruit).all(db).await?;\\n```\\n\\nConsider the above API, `Cake` and `Fruit` are two separate models.\\n\\nIf you come from a dynamic language, you\'d probably used to:\\n\\n```rust\\nstruct Cake {\\n id: u64,\\n fruit: Fruit,\\n ..\\n}\\n```\\n\\nIt\'s so convenient that you can simply:\\n\\n```rust\\nlet cake = Cake::find().one(db).await?;\\nprintln!(\\"Fruit = {}\\", cake.fruit.name);\\n```\\n\\nSweet right? Okay so, the problem with this pattern is that it does not fit well with Rust.\\n\\nLet\'s look at this playground: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=6fb0a981189ace081fbb2aa04f50146b\\n\\n```rust\\nstruct Parent {\\n a: u64,\\n child: Option,\\n}\\n\\nstruct ParentWithBox {\\n a: u64,\\n child: Option>,\\n}\\n\\nstruct Child {\\n a: u64,\\n b: u64,\\n c: u64,\\n d: u64,\\n}\\n\\nfn main() {\\n dbg!(std::mem::size_of::());\\n dbg!(std::mem::size_of::());\\n dbg!(std::mem::size_of::());\\n}\\n```\\n\\nWhat\'s the output you guess?\\n\\n```log\\n[src/main.rs:21] std::mem::size_of::() = 48\\n[src/main.rs:22] std::mem::size_of::() = 16\\n[src/main.rs:23] std::mem::size_of::() = 32\\n```\\n\\nIn dynamic languages, objects are always held by pointers, and that maps to a `Box` in Rust. In Rust, we don\'t put objects in `Box`es 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.\\n\\nRef:\\n1. https://users.rust-lang.org/t/how-to-create-large-objects-directly-in-heap/26405\\n2. https://github.com/PoignardAzur/placement-by-return/blob/placement-by-return/text/0000-placement-by-return.md\\n\\nWe 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.\\n\\nIf 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.\\n\\nThat 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."},{"id":"2022-04-04-introducing-starfish-ql","metadata":{"permalink":"/blog/2022-04-04-introducing-starfish-ql","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-04-04-introducing-starfish-ql.md","source":"@site/blog/2022-04-04-introducing-starfish-ql.md","title":"Introducing StarfishQL","description":"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.","date":"2022-04-04T00:00:00.000Z","formattedDate":"April 4, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":7.055,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-04-04-introducing-starfish-ql","title":"Introducing StarfishQL","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"SeaORM FAQ.01","permalink":"/blog/2022-05-14-faq-01"},"nextItem":{"title":"What\'s new in SeaORM 0.7.0","permalink":"/blog/2022-03-26-whats-new-in-0.7.0"}},"content":"We are pleased to introduce [StarfishQL](https://www.sea-ql.org/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.\\n\\n## Motivation\\n\\nStarfishQL is a framework for providing a graph database and a graph query engine that interacts with it.\\n\\nA concrete example (Freeport) involving the graph of crate dependency on [crates.io](https://crates.io/) is used for illustration. With this example, you can see StarfishQL in action.\\n\\nAt 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.\\n\\nStarfishQL\'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](https://starfish-ql.sea-ql.org/).\\n\\n
\\n
\\n
\\n \\n
\\n \\n

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

\\n
\\n
\\n
\\n
\\n
\\n \\n
\\n \\n

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

\\n
\\n
\\n
\\n
\\n\\n## Design\\n\\nIn 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.\\n\\nIn the case of a graph query engine, the output data is a graph ([wiki](https://en.wikipedia.org/wiki/Graph_(abstract_data_type))).\\n\\n![Graph query engine overview](https://www.sea-ql.org/StarfishQL/img/graph_query_engine_overview.png)\\n\\nIn 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.\\n\\n## Implementation\\n\\nIn the example of Freeport, StarfishQL consists of the following three components.\\n\\n### Graph Query Engine\\n\\nAs a core component of StarfishQL, the graph query engine is a Rust backend application powered by the [Rocket](https://crates.io/crates/rocket) web framework and the [SeaQL ecosystem](https://github.com/SeaQL).\\n\\nThe engine listens at the following endpoints for the corresponding operation:\\n- `/schema` - [Define/Reset the schema](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/defining-graph-schema)\\n- `/mutate` - [Perform mutate operations](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/mutate-operations)\\n- `/query` - [Perform queries](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/querying-graph-data)\\n\\nYou could also invoke the endpoints above programmatically.\\n\\nGraph data are stored in a relational database:\\n- [Metadata](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/data-storage) - Definition of each entity and relation, e.g. attributes of crates and dependency\\n- [Node Data](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/data-storage#storage-of-entities) - An instance of an entity, e.g. crate name and version number\\n- [Edge Data](https://www.sea-ql.org/StarfishQL/docs/architecture-of-graph-query-engine/data-storage#storage-of-relations) - An instance of a relation, e.g. one crate depends on another\\n\\n### crates.io Crawler\\n\\nTo obtain the crate data to insert into the database, we used a [fast, non-disruptive crawler](https://www.sea-ql.org/StarfishQL/docs/architecture-of-crates-io-crawler/overview) on a local clone of the public index repo of crates.io.\\n\\n### Graph Visualization\\n\\nWe used [`d3.js`](https://d3js.org/) to create force-directed graphs to display the results. The two [colourful graphs](#colourful-graphs) above are such products.\\n\\n## Findings\\n\\nHere are some interesting findings we made during the process.\\n\\n
\\n
\\n
\\n
\\n

Top-10 Dependencies

\\n
\\n
\\n

List of top 10 crates order by different decay modes.

\\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
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
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
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
\\n
\\n
\\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n \\n
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
\\n
\\n
\\n

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

\\n

\\n 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.\\n

\\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n
\\n

Number of crates without Dependencies

\\n
\\n
\\n

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

\\n

\\n e.g. \\n a, \\n a-, \\n a0, \\n ..., \\n zyx_test, \\n zz-buffer, \\n z_table\\n

\\n

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

\\n \\n \\n
\\n
\\n
\\n
\\n\\n
\\n
\\n
\\n
\\n

Number of crates without Dependants

\\n
\\n
\\n

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

\\n

\\n e.g. \\n a, \\n a-, \\n a-bot, \\n ..., \\n zzp-tools, \\n zzz, \\n z_table\\n

\\n

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

\\n
\\n
\\n
\\n
\\n\\n*As of March 30, 2022*\\n\\n## Conclusion\\n\\nStarfishQL allows flexible and portable definition, manipulation, retrieval, and visualization of graph data.\\n\\nThe 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.\\n\\nAdmittedly, 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](https://www.sea-ql.org/SeaORM/), together we are one step closer to the vision of Rust for data engineering.\\n\\n## People\\n\\nStarfishQL is created by the following SeaQL team members:\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sanford Pun\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Contributing\\n\\nWe are super excited to be selected as a Google Summer of Code 2022 mentor organization!\\n\\nStarfishQL is one of the [GSoC project ideas](https://github.com/SeaQL/summer-of-code/blob/main/2022/README.md#2-a-graph-database-and-query-engine-codename-starfishql-for-graph-analysis-and-visualization) that opens for development proposals. Join us on GSoC 2022 by following the instructions on [GSoC Contributing Guide](https://github.com/SeaQL/summer-of-code/blob/main/2022/CONTRIBUTING.md)."},{"id":"2022-03-26-whats-new-in-0.7.0","metadata":{"permalink":"/blog/2022-03-26-whats-new-in-0.7.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-03-26-whats-new-in-0.7.0.md","source":"@site/blog/2022-03-26-whats-new-in-0.7.0.md","title":"What\'s new in SeaORM 0.7.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.7.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2022-03-26T00:00:00.000Z","formattedDate":"March 26, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.965,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-03-26-whats-new-in-0.7.0","title":"What\'s new in SeaORM 0.7.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Introducing StarfishQL","permalink":"/blog/2022-04-04-introducing-starfish-ql"},"nextItem":{"title":"Google Summer of Code 2022","permalink":"/blog/gsoc-2022"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.7.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.7.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Update ActiveModel by JSON\\n\\n[[#492](https://github.com/SeaQL/sea-orm/pull/492)] If you want to save user input into the database you can easily convert JSON value into ActiveModel.\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]\\n#[sea_orm(table_name = \\"fruit\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n #[serde(skip_deserializing)] // Skip deserializing\\n pub id: i32,\\n pub name: String,\\n pub cake_id: Option,\\n}\\n```\\n\\nSet the attributes in ActiveModel with `set_from_json` method.\\n\\n```rust\\n// A ActiveModel with primary key set\\nlet mut fruit = fruit::ActiveModel {\\n id: ActiveValue::Set(1),\\n name: ActiveValue::NotSet,\\n cake_id: ActiveValue::NotSet,\\n};\\n\\n// Note that this method will not alter the primary key values in ActiveModel\\nfruit.set_from_json(json!({\\n \\"id\\": 8,\\n \\"name\\": \\"Apple\\",\\n \\"cake_id\\": 1,\\n}))?;\\n\\nassert_eq!(\\n fruit,\\n fruit::ActiveModel {\\n id: ActiveValue::Set(1),\\n name: ActiveValue::Set(\\"Apple\\".to_owned()),\\n cake_id: ActiveValue::Set(Some(1)),\\n }\\n);\\n```\\n\\nCreate a new ActiveModel from JSON value with the `from_json` method.\\n\\n```rust\\nlet fruit = fruit::ActiveModel::from_json(json!({\\n \\"name\\": \\"Apple\\",\\n}))?;\\n\\nassert_eq!(\\n fruit,\\n fruit::ActiveModel {\\n id: ActiveValue::NotSet,\\n name: ActiveValue::Set(\\"Apple\\".to_owned()),\\n cake_id: ActiveValue::NotSet,\\n }\\n);\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n qltk\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Support `time` crate in Model\\n\\n[[#602](https://github.com/SeaQL/sea-orm/pull/602)] You can define datetime column in Model with `time` crate. You can migrate your Model originally defined in `chrono` to `time` crate.\\n\\nModel defined in `chrono` crate.\\n\\n```rust\\nuse sea_orm::entity::prelude::*;\\n\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"transaction_log\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub date: Date, // chrono::NaiveDate\\n pub time: Time, // chrono::NaiveTime\\n pub date_time: DateTime, // chrono::NaiveDateTime\\n pub date_time_tz: DateTimeWithTimeZone, // chrono::DateTime\\n}\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\\npub enum Relation {}\\n\\nimpl ActiveModelBehavior for ActiveModel {}\\n```\\n\\nModel defined in `time` crate.\\n\\n```rust\\nuse sea_orm::entity::prelude::*;\\n\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"transaction_log\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub date: TimeDate, // time::Date\\n pub time: TimeTime, // time::Time\\n pub date_time: TimeDateTime, // time::PrimitiveDateTime\\n pub date_time_tz: TimeDateTimeWithTimeZone, // time::OffsetDateTime\\n}\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]\\npub enum Relation {}\\n\\nimpl ActiveModelBehavior for ActiveModel {}\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Tom Hacohen\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Delete by Primary Key\\n\\n[[#590](https://github.com/SeaQL/sea-orm/pull/590)] Instead of selecting `Model` from the database then deleting it. You could also delete a row from database directly by its primary key.\\n\\n```rust\\nlet res: DeleteResult = Fruit::delete_by_id(38).exec(db).await?;\\nassert_eq!(res.rows_affected, 1);\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Shouvik Ghosh\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Zhenwei Guo\\n
\\n
\\n
\\n
\\n
\\n\\n## Paginate Results from Raw Query\\n\\n[[#617](https://github.com/SeaQL/sea-orm/pull/617)] You can paginate [`SelectorRaw`](https://docs.rs/sea-orm/0.6.0/sea_orm/struct.SelectorRaw.html) and fetch `Model` in batch.\\n\\n```rust\\nlet mut cake_pages = cake::Entity::find()\\n .from_raw_sql(Statement::from_sql_and_values(\\n DbBackend::Postgres,\\n r#\\"SELECT \\"cake\\".\\"id\\", \\"cake\\".\\"name\\" FROM \\"cake\\" WHERE \\"id\\" = $1\\"#,\\n vec![1.into()],\\n ))\\n .paginate(db, 50);\\n \\nwhile let Some(cakes) = cake_pages.fetch_and_next().await? {\\n // Do something on cakes: Vec\\n}\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Bastian\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n shinbunbun\\n
\\n
\\n
\\n
\\n
\\n\\n## Create Database Index\\n\\n[[#593](https://github.com/SeaQL/sea-orm/pull/593)] To create indexes in database instead of writing [`IndexCreateStatement`](https://docs.rs/sea-query/*/sea_query/index/struct.IndexCreateStatement.html) manually, you can derive it from `Entity` using [`Schema::create_index_from_entity`](https://docs.rs/sea-orm/0.5/sea_orm/schema/struct.Schema.html#method.create_index_from_entity).\\n\\n```rust\\nuse sea_orm::{sea_query, tests_cfg::*, Schema};\\n\\nlet builder = db.get_database_backend();\\nlet schema = Schema::new(builder);\\n\\nlet stmts = schema.create_index_from_entity(indexes::Entity);\\nassert_eq!(stmts.len(), 2);\\n\\nlet idx = sea_query::Index::create()\\n .name(\\"idx-indexes-index1_attr\\")\\n .table(indexes::Entity)\\n .col(indexes::Column::Index1Attr)\\n .to_owned();\\nassert_eq!(builder.build(&stmts[0]), builder.build(&idx));\\n\\nlet idx = sea_query::Index::create()\\n .name(\\"idx-indexes-index2_attr\\")\\n .table(indexes::Entity)\\n .col(indexes::Column::Index2Attr)\\n .to_owned();\\nassert_eq!(builder.build(&stmts[1]), builder.build(&idx));\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Jochen G\xf6rtler\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Nick Burrett\\n
\\n
\\n
\\n
\\n
\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Dean Sheather\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.8.x`](https://github.com/SeaQL/sea-orm/milestone/8).\\n\\n## GSoC 2022\\n\\nWe are super excited to be selected as a Google Summer of Code 2022 mentor organization. Prospective contributors, please visit our [GSoC 2022 Organization Profile](https://summerofcode.withgoogle.com/programs/2022/organizations/seaql)!"},{"id":"gsoc-2022","metadata":{"permalink":"/blog/gsoc-2022","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-03-08-gsoc-2022.md","source":"@site/blog/2022-03-08-gsoc-2022.md","title":"Google Summer of Code 2022","description":"GSoC 2022 Organization Profile","date":"2022-03-08T00:00:00.000Z","formattedDate":"March 8, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":1.555,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"gsoc-2022","title":"Google Summer of Code 2022","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.7.0","permalink":"/blog/2022-03-26-whats-new-in-0.7.0"},"nextItem":{"title":"What\'s new in SeaORM 0.6.0","permalink":"/blog/2022-02-07-whats-new-in-0.6.0"}},"content":"[GSoC 2022 Organization Profile](https://summerofcode.withgoogle.com/programs/2022/organizations/seaql)\\n\\nWe 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!\\n\\nIn 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.\\n\\nThe first piece of tool we released is [SeaQuery](https://github.com/SeaQL/sea-query), 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.\\n\\nThe second piece of tool is [SeaSchema](https://github.com/SeaQL/sea-schema), 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.\\n\\nThe third piece of tool is [SeaORM](https://github.com/SeaQL/sea-orm), 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.\\n\\nBut why stops at three?\\n\\nThis 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!\\n\\nYour participation is what makes us unique; your adoption is what drives us forward.\\n\\nThank 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.\\n\\nProspective contributors, stay in touch with us. We also welcome any discussion on the future of the Rust ecosystem and the SeaQL organization.\\n\\n[GSoC 2022 Idea List](https://github.com/SeaQL/summer-of-code/blob/main/2022/README.md)"},{"id":"2022-02-07-whats-new-in-0.6.0","metadata":{"permalink":"/blog/2022-02-07-whats-new-in-0.6.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-02-07-whats-new-in-0.6.0.md","source":"@site/blog/2022-02-07-whats-new-in-0.6.0.md","title":"What\'s new in SeaORM 0.6.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.6.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2022-02-07T00:00:00.000Z","formattedDate":"February 7, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.4,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-02-07-whats-new-in-0.6.0","title":"What\'s new in SeaORM 0.6.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"Google Summer of Code 2022","permalink":"/blog/gsoc-2022"},"nextItem":{"title":"What\'s new in SeaORM 0.5.0","permalink":"/blog/2022-01-01-whats-new-in-0.5.0"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.6.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.6.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Migration\\n\\n[[#335](https://github.com/SeaQL/sea-orm/pull/335)] Version control you database schema with migrations written in SeaQuery or in raw SQL. View [migration docs](https://www.sea-ql.org/SeaORM/docs/migration/setting-up-migration) to learn more.\\n\\n1. Setup the migration directory by executing `sea-orm-cli migrate init`.\\n ```\\n migration\\n \u251c\u2500\u2500 Cargo.toml\\n \u251c\u2500\u2500 README.md\\n \u2514\u2500\u2500 src\\n \u251c\u2500\u2500 lib.rs\\n \u251c\u2500\u2500 m20220101_000001_create_table.rs\\n \u2514\u2500\u2500 main.rs\\n ```\\n2. Defines the migration in SeaQuery.\\n ```rust\\n use sea_schema::migration::prelude::*;\\n\\n pub struct Migration;\\n\\n impl MigrationName for Migration {\\n fn name(&self) -> &str {\\n \\"m20220101_000001_create_table\\"\\n }\\n }\\n\\n #[async_trait::async_trait]\\n impl MigrationTrait for Migration {\\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n manager\\n .create_table( ... )\\n .await\\n }\\n\\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {\\n manager\\n .drop_table( ... )\\n .await\\n }\\n }\\n ```\\n3. Apply the migration by executing `sea-orm-cli migrate`.\\n ```shell\\n $ sea-orm-cli migrate\\n Applying all pending migrations\\n Applying migration \'m20220101_000001_create_table\'\\n Migration \'m20220101_000001_create_table\' has been applied\\n ```\\n\\n
\\n
\\n Designed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Support DateTimeUtc & DateTimeLocal in Model\\n\\n[[#489](https://github.com/SeaQL/sea-orm/pull/489)] Represents database\'s timestamp column in Model with attribute of type `DateTimeLocal` (`chrono::DateTime`) or `DateTimeUtc` (`chrono::DateTime`).\\n\\n```rust\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"satellite\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n pub satellite_name: String,\\n pub launch_date: DateTimeUtc,\\n pub deployment_date: DateTimeLocal,\\n}\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n lz1998\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Charles\xb7Chege\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Mock Join Results\\n\\n[[#455](https://github.com/SeaQL/sea-orm/pull/455)] Constructs mock results of related model with tuple of model.\\n\\n```rust\\nlet db = MockDatabase::new(DbBackend::Postgres)\\n // Mocking result of cake with its related fruit\\n .append_query_results(vec![vec![(\\n cake::Model {\\n id: 1,\\n name: \\"Apple Cake\\".to_owned(),\\n },\\n fruit::Model {\\n id: 2,\\n name: \\"Apple\\".to_owned(),\\n cake_id: Some(1),\\n },\\n )]])\\n .into_connection();\\n\\nassert_eq!(\\n cake::Entity::find()\\n .find_also_related(fruit::Entity)\\n .all(&db)\\n .await?,\\n vec![(\\n cake::Model {\\n id: 1,\\n name: \\"Apple Cake\\".to_owned(),\\n },\\n Some(fruit::Model {\\n id: 2,\\n name: \\"Apple\\".to_owned(),\\n cake_id: Some(1),\\n })\\n )]\\n);\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Bastian\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Bastian\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Support Max Connection Lifetime Option\\n\\n[[#493](https://github.com/SeaQL/sea-orm/pull/493)] You can set the maximum lifetime of individual connection with the `max_lifetime` method.\\n\\n```rust\\nlet mut opt = ConnectOptions::new(\\"protocol://username:password@host/database\\".to_owned());\\nopt.max_lifetime(Duration::from_secs(8))\\n .max_connections(100)\\n .min_connections(5)\\n .connect_timeout(Duration::from_secs(8))\\n .idle_timeout(Duration::from_secs(8))\\n .sqlx_logging(true);\\n\\nlet db = Database::connect(opt).await?;\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n\\n## SeaORM CLI & Codegen Updates\\n\\n- [[#433](https://github.com/SeaQL/sea-orm/pull/433)] Generates the `column_name` macro attribute for column which is not named in snake case\\n- [[#335](https://github.com/SeaQL/sea-orm/pull/335)] Introduces migration subcommands `sea-orm-cli migrate`\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Gabriel Paulucci\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n \xc9mile Fugulin\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.7.x`](https://github.com/SeaQL/sea-orm/milestone/7)."},{"id":"2022-01-01-whats-new-in-0.5.0","metadata":{"permalink":"/blog/2022-01-01-whats-new-in-0.5.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2022-01-01-whats-new-in-0.5.0.md","source":"@site/blog/2022-01-01-whats-new-in-0.5.0.md","title":"What\'s new in SeaORM 0.5.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.5.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2022-01-01T00:00:00.000Z","formattedDate":"January 1, 2022","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.98,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2022-01-01-whats-new-in-0.5.0","title":"What\'s new in SeaORM 0.5.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.6.0","permalink":"/blog/2022-02-07-whats-new-in-0.6.0"},"nextItem":{"title":"What\'s new in SeaORM 0.4.0","permalink":"/blog/2021-11-19-whats-new-in-0.4.0"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.5.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.5.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Insert and Update Return `Model`\\n\\n[[#339](https://github.com/SeaQL/sea-orm/pull/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`.\\n\\nBreaking Changes:\\n- `ActiveModel::insert` and `ActiveModel::update` return `Model` instead of `ActiveModel`\\n- Method `ActiveModelBehavior::after_save` takes `Model` as input instead of `ActiveModel`\\n\\n```rust\\n// Construct a `ActiveModel`\\nlet active_model = ActiveModel {\\n name: Set(\\"Classic Vanilla Cake\\".to_owned()),\\n ..Default::default()\\n};\\n// Do insert\\nlet cake: Model = active_model.insert(db).await?;\\nassert_eq!(\\n cake,\\n Model {\\n id: 1,\\n name: \\"Classic Vanilla Cake\\".to_owned(),\\n }\\n);\\n\\n// Covert `Model` into `ActiveModel`\\nlet mut active_model = cake.into_active_model();\\n// Change the name of cake\\nactive_model.name = Set(\\"Chocolate Cake\\".to_owned());\\n// Do update\\nlet cake: Model = active_model.update(db).await?;\\nassert_eq!(\\n cake,\\n Model {\\n id: 1,\\n name: \\"Chocolate Cake\\".to_owned(),\\n }\\n);\\n\\n// Do delete\\ncake.delete(db).await?;\\n```\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Julien Nicoulaud\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Edgar\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## `ActiveValue` Revamped\\n\\n[[#340](https://github.com/SeaQL/sea-orm/pull/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.\\n\\nBreaking Changes:\\n- Rename method `sea_orm::unchanged_active_value_not_intended_for_public_use` to `sea_orm::Unchanged`\\n- Rename method `ActiveValue::unset` to `ActiveValue::not_set`\\n- Rename method `ActiveValue::is_unset` to `ActiveValue::is_not_set`\\n- `PartialEq` of `ActiveValue` will also check the equality of state instead of just checking the equality of value\\n\\n```rust\\n/// Defines a stateful value used in ActiveModel.\\npub enum ActiveValue\\nwhere\\n V: Into,\\n{\\n /// A defined [Value] actively being set\\n Set(V),\\n /// A defined [Value] remain unchanged\\n Unchanged(V),\\n /// An undefined [Value]\\n NotSet,\\n}\\n```\\n\\n
\\n
\\n Designed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## SeaORM CLI & Codegen Updates\\n\\nInstall latest version of `sea-orm-cli`:\\n\\n```shell\\ncargo install sea-orm-cli\\n```\\n\\nUpdates related to entity files generation (`cargo generate entity`):\\n\\n- [[#348](https://github.com/SeaQL/sea-orm/pull/348)] Discovers and defines PostgreSQL enums\\n- [[#386](https://github.com/SeaQL/sea-orm/pull/386)] Supports SQLite database, you can generate entity files from all supported databases including MySQL, PostgreSQL and SQLite\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Charles\xb7Chege\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Tracing\\n\\n[[#373](https://github.com/SeaQL/sea-orm/pull/373)] You can trace the query executed by SeaORM with `debug-print` feature enabled and [`tracing-subscriber`](https://crates.io/crates/tracing-subscriber) up and running.\\n\\n```rust\\npub async fn main() {\\n tracing_subscriber::fmt()\\n .with_max_level(tracing::Level::DEBUG)\\n .with_test_writer()\\n .init();\\n\\n // ...\\n}\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n
\\n
\\n
\\n
\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sakti Dwi Cahyono\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Praveen Perera\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.6.x`](https://github.com/SeaQL/sea-orm/milestone/6)."},{"id":"2021-11-19-whats-new-in-0.4.0","metadata":{"permalink":"/blog/2021-11-19-whats-new-in-0.4.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-11-19-whats-new-in-0.4.0.md","source":"@site/blog/2021-11-19-whats-new-in-0.4.0.md","title":"What\'s new in SeaORM 0.4.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.4.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2021-11-19T00:00:00.000Z","formattedDate":"November 19, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.13,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2021-11-19-whats-new-in-0.4.0","title":"What\'s new in SeaORM 0.4.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.5.0","permalink":"/blog/2022-01-01-whats-new-in-0.5.0"},"nextItem":{"title":"What\'s new in SeaORM 0.3.0","permalink":"/blog/2021-10-15-whats-new-in-0.3.0"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.4.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.4.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Rust Edition 2021\\n\\n[[#273](https://github.com/SeaQL/sea-orm/pull/273)] Upgrading SeaORM to [Rust Edition 2021](https://blog.rust-lang.org/2021/10/21/Rust-1.56.0.html#rust-2021) \ud83e\udd80\u2764\ud83d\udc1a!\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Carter Snook\\n
\\n
\\n
\\n
\\n
\\n\\n## Enumeration\\n\\n[[#252](https://github.com/SeaQL/sea-orm/issues/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](https://www.sea-ql.org/SeaORM/docs/generate-entity/enumeration).\\n\\n```rust\\n#[derive(Debug, Clone, PartialEq, DeriveEntityModel)]\\n#[sea_orm(table_name = \\"active_enum\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n // Use our custom enum in a model\\n pub category: Option,\\n pub color: Option,\\n pub tea: Option,\\n}\\n\\n#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]\\n#[sea_orm(rs_type = \\"String\\", db_type = \\"String(Some(1))\\")]\\n// An enum serialized into database as a string value\\npub enum Category {\\n #[sea_orm(string_value = \\"B\\")]\\n Big,\\n #[sea_orm(string_value = \\"S\\")]\\n Small,\\n}\\n\\n#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]\\n#[sea_orm(rs_type = \\"i32\\", db_type = \\"Integer\\")]\\n// An enum serialized into database as an integer value\\npub enum Color {\\n #[sea_orm(num_value = 0)]\\n Black,\\n #[sea_orm(num_value = 1)]\\n White,\\n}\\n\\n#[derive(Debug, Clone, PartialEq, EnumIter, DeriveActiveEnum)]\\n#[sea_orm(rs_type = \\"String\\", db_type = \\"Enum\\", enum_name = \\"tea\\")]\\n// An enum serialized into database as a database native enum\\npub enum Tea {\\n #[sea_orm(string_value = \\"EverydayTea\\")]\\n EverydayTea,\\n #[sea_orm(string_value = \\"BreakfastTea\\")]\\n BreakfastTea,\\n}\\n```\\n\\n\\n
\\n
\\n Designed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Supports `RETURNING` Clause on PostgreSQL\\n\\n[[#183](https://github.com/SeaQL/sea-orm/issues/183)] When performing insert or update operation on `ActiveModel` against PostgreSQL, `RETURNING` clause will be used to perform select in a single SQL statement.\\n\\n```rust\\n// For PostgreSQL\\ncake::ActiveModel {\\n name: Set(\\"Apple Pie\\".to_owned()),\\n ..Default::default()\\n}\\n.insert(&postgres_db)\\n.await?;\\n\\nassert_eq!(\\n postgres_db.into_transaction_log(),\\n vec![Transaction::from_sql_and_values(\\n DbBackend::Postgres,\\n r#\\"INSERT INTO \\"cake\\" (\\"name\\") VALUES ($1) RETURNING \\"id\\", \\"name\\"\\"#,\\n vec![\\"Apple Pie\\".into()]\\n )]);\\n```\\n\\n```rust\\n// For MySQL & SQLite\\ncake::ActiveModel {\\n name: Set(\\"Apple Pie\\".to_owned()),\\n ..Default::default()\\n}\\n.insert(&other_db)\\n.await?;\\n\\nassert_eq!(\\n other_db.into_transaction_log(),\\n vec![\\n Transaction::from_sql_and_values(\\n DbBackend::MySql,\\n r#\\"INSERT INTO `cake` (`name`) VALUES (?)\\"#,\\n vec![\\"Apple Pie\\".into()]\\n ),\\n Transaction::from_sql_and_values(\\n DbBackend::MySql,\\n r#\\"SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = ? LIMIT ?\\"#,\\n vec![15.into(), 1u64.into()]\\n )]);\\n```\\n\\n\\n
\\n
\\n Proposed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Marlon Brand\xe3o de Sousa\\n
\\n
\\n
\\n
\\n
\\n Contributed by:\\n

\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Axum Integration Example\\n\\n[[#297](https://github.com/SeaQL/sea-orm/pull/297)] Added [Axum integration example](https://github.com/SeaQL/sea-orm/tree/master/examples/axum_example). More examples [wanted](https://github.com/SeaQL/sea-orm/issues/269)!\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Yoshiera\\n
\\n
\\n
\\n
\\n
\\n\\n## Sponsor\\n\\nOur [GitHub Sponsor](https://github.com/sponsors/SeaQL) profile is up! If you feel generous, a small donation will be greatly appreciated.\\n\\nA big shout out to our first sponsors \ud83d\ude07:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Shane Sveller\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Zachary Vander Velden\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Unnamed Sponsor\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.5.x`](https://github.com/SeaQL/sea-orm/milestone/5)."},{"id":"2021-10-15-whats-new-in-0.3.0","metadata":{"permalink":"/blog/2021-10-15-whats-new-in-0.3.0","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-10-15-whats-new-in-0.3.0.md","source":"@site/blog/2021-10-15-whats-new-in-0.3.0.md","title":"What\'s new in SeaORM 0.3.0","description":"\ud83c\udf89 We are pleased to release SeaORM 0.3.0 today! Here are some feature highlights \ud83c\udf1f:","date":"2021-10-15T00:00:00.000Z","formattedDate":"October 15, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":3.855,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2021-10-15-whats-new-in-0.3.0","title":"What\'s new in SeaORM 0.3.0","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.4.0","permalink":"/blog/2021-11-19-whats-new-in-0.4.0"},"nextItem":{"title":"What\'s new in SeaORM 0.2.4","permalink":"/blog/2021-10-01-whats-new-in-0.2.4"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.3.0`](https://github.com/SeaQL/sea-orm/releases/tag/0.3.0) today! Here are some feature highlights \ud83c\udf1f:\\n\\n## Transaction\\n\\n[[#222](https://github.com/SeaQL/sea-orm/pull/222)] Use database transaction to perform atomic operations\\n\\nTwo transaction APIs are provided:\\n\\n- `closure` style. Will be committed on Ok and rollback on Err.\\n ```rust\\n // -> Result\\n db.transaction::<_, _, DbErr>(|txn| {\\n Box::pin(async move {\\n bakery::ActiveModel {\\n name: Set(\\"SeaSide Bakery\\".to_owned()),\\n ..Default::default()\\n }\\n .save(txn)\\n .await?;\\n\\n bakery::ActiveModel {\\n name: Set(\\"Top Bakery\\".to_owned()),\\n ..Default::default()\\n }\\n .save(txn)\\n .await?;\\n\\n Ok(())\\n })\\n })\\n .await;\\n ```\\n\\n- RAII style. `begin` the transaction followed by `commit` or `rollback`. If `txn` goes out of scope, it\'d automatically rollback.\\n ```rust\\n let txn = db.begin().await?;\\n\\n // do something with txn\\n\\n txn.commit().await?;\\n ```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n\\n## Streaming\\n\\n[[#222](https://github.com/SeaQL/sea-orm/pull/222)] Use async stream on any `Select` for memory efficiency.\\n\\n```rust\\nlet mut stream = Fruit::find().stream(&db).await?;\\n\\nwhile let Some(item) = stream.try_next().await? {\\n let item: fruit::ActiveModel = item.into();\\n // do something with item\\n}\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n
\\n
\\n
\\n
\\n\\n## API for custom logic on save & delete\\n\\n[[#210](https://github.com/SeaQL/sea-orm/pull/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.\\n\\n```rust\\nimpl ActiveModelBehavior for ActiveModel {\\n // Override default values\\n fn new() -> Self {\\n Self {\\n serial: Set(Uuid::new_v4()),\\n ..ActiveModelTrait::default()\\n }\\n }\\n\\n // Triggered before insert / update\\n fn before_save(self, insert: bool) -> Result {\\n if self.price.as_ref() <= &0.0 {\\n Err(DbErr::Custom(format!(\\n \\"[before_save] Invalid Price, insert: {}\\",\\n insert\\n )))\\n } else {\\n Ok(self)\\n }\\n }\\n\\n // Triggered after insert / update\\n fn after_save(self, insert: bool) -> Result {\\n Ok(self)\\n }\\n\\n // Triggered before delete\\n fn before_delete(self) -> Result {\\n Ok(self)\\n }\\n\\n // Triggered after delete\\n fn after_delete(self) -> Result {\\n Ok(self)\\n }\\n}\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Muhannad\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n\\n## Generate Entity Models That Derive Serialize / Deserialize\\n\\n[[#237](https://github.com/SeaQL/sea-orm/pull/237)] You can use `sea-orm-cli` to generate entity models that also derive serde `Serialize` / `Deserialize` traits.\\n\\n```rust\\n//! SeaORM Entity. Generated by sea-orm-codegen 0.3.0\\n\\nuse sea_orm::entity::prelude:: * ;\\nuse serde::{Deserialize, Serialize};\\n\\n#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Serialize, Deserialize)]\\n#[sea_orm(table_name = \\"cake\\")]\\npub struct Model {\\n #[sea_orm(primary_key)]\\n pub id: i32,\\n #[sea_orm(column_type = \\"Text\\", nullable)]\\n pub name: Option ,\\n}\\n\\n// ...\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Tim Eggert\\n
\\n
\\n
\\n
\\n
\\n\\n## Introduce `DeriveIntoActiveModel` macro & `IntoActiveValue` Trait\\n\\n[[#240](https://github.com/SeaQL/sea-orm/pull/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.\\n\\n`IntoActiveValue` trait allows converting `Option` into `ActiveValue` with the method `into_active_value`.\\n\\n```rust\\n// Define regular model as usual\\n#[derive(Clone, Debug, PartialEq, DeriveModel, DeriveActiveModel)]\\n#[sea_orm(table_name = \\"users\\")]\\npub struct Model {\\n pub id: Uuid,\\n pub created_at: DateTimeWithTimeZone,\\n pub updated_at: DateTimeWithTimeZone,\\n pub email: String,\\n pub password: String,\\n pub full_name: Option,\\n pub phone: Option,\\n}\\n\\n// Create a new struct with some fields omitted\\n#[derive(DeriveIntoActiveModel)]\\npub struct NewUser {\\n // id, created_at and updated_at are omitted from this struct,\\n // and will always be `ActiveValue::unset`\\n pub email: String,\\n pub password: String,\\n // Full name is usually optional, but it can be required here\\n pub full_name: String,\\n // Option implements `IntoActiveValue`, and when `None` will be `unset`\\n pub phone: Option,\\n}\\n\\n#[derive(DeriveIntoActiveModel)]\\npub struct UpdateUser {\\n // Option> allows for Some(None) to update the column to be NULL\\n pub phone: Option>,\\n}\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ari Seyhun\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.4.x`](https://github.com/SeaQL/sea-orm/milestone/4)."},{"id":"2021-10-01-whats-new-in-0.2.4","metadata":{"permalink":"/blog/2021-10-01-whats-new-in-0.2.4","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-10-01-whats-new-in-0.2.4.md","source":"@site/blog/2021-10-01-whats-new-in-0.2.4.md","title":"What\'s new in SeaORM 0.2.4","description":"\ud83c\udf89 We are pleased to release SeaORM 0.2.4 today! Some feature highlights:","date":"2021-10-01T00:00:00.000Z","formattedDate":"October 1, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":1.76,"hasTruncateMarker":false,"authors":[{"name":"SeaQL Team","title":"Chris Tsang","url":"https://github.com/SeaQL","imageURL":"https://www.sea-ql.org/SeaORM/img/SeaQL.png"}],"frontMatter":{"slug":"2021-10-01-whats-new-in-0.2.4","title":"What\'s new in SeaORM 0.2.4","author":"SeaQL Team","author_title":"Chris Tsang","author_url":"https://github.com/SeaQL","author_image_url":"https://www.sea-ql.org/SeaORM/img/SeaQL.png","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.3.0","permalink":"/blog/2021-10-15-whats-new-in-0.3.0"},"nextItem":{"title":"Introducing SeaORM \ud83d\udc1a","permalink":"/blog/2021-09-20-introducing-sea-orm"}},"content":"\ud83c\udf89 We are pleased to release SeaORM [`0.2.4`](https://github.com/SeaQL/sea-orm/releases/tag/0.2.4) today! Some feature highlights:\\n\\n## Better ergonomic when working with custom select list\\n\\n[[#208](https://github.com/SeaQL/sea-orm/pull/208)] Use [Select::into_values](https://docs.rs/sea-orm/0.2.4/sea_orm/entity/prelude/struct.Select.html#method.into_values) to quickly select a custom column list and destruct as tuple.\\n\\n```rust\\nuse sea_orm::{entity::*, query::*, tests_cfg::cake, DeriveColumn, EnumIter};\\n\\n#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]\\nenum QueryAs {\\n CakeName,\\n NumOfCakes,\\n}\\n\\nlet res: Vec<(String, i64)> = cake::Entity::find()\\n .select_only()\\n .column_as(cake::Column::Name, QueryAs::CakeName)\\n .column_as(cake::Column::Id.count(), QueryAs::NumOfCakes)\\n .group_by(cake::Column::Name)\\n .into_values::<_, QueryAs>()\\n .all(&db)\\n .await?;\\n\\nassert_eq!(\\n res,\\n vec![(\\"Chocolate Forest\\".to_owned(), 2i64)]\\n);\\n```\\n\\nContributed by:\\n\\n
\\n
\\n \\n \\n \\n
\\n
\\n Muhannad\\n
\\n
\\n
\\n
\\n\\n## Rename column name & column enum variant\\n\\n[[#209](https://github.com/SeaQL/sea-orm/pull/209)] Rename the column name and enum variant of a model attribute, especially helpful when the column name is a Rust keyword.\\n\\n```rust\\nmod my_entity {\\n use sea_orm::entity::prelude::*;\\n\\n #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]\\n #[sea_orm(table_name = \\"my_entity\\")]\\n pub struct Model {\\n #[sea_orm(primary_key, enum_name = \\"IdentityColumn\\", column_name = \\"id\\")]\\n pub id: i32,\\n #[sea_orm(column_name = \\"type\\")]\\n pub type_: String,\\n }\\n\\n //...\\n}\\n\\nassert_eq!(my_entity::Column::IdentityColumn.to_string().as_str(), \\"id\\");\\nassert_eq!(my_entity::Column::Type.to_string().as_str(), \\"type\\");\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## `not` on a condition tree\\n\\n[[#145](https://github.com/SeaQL/sea-query/pull/145)] Build a complex condition tree with [Condition](https://docs.rs/sea-query/0.16.5/sea_query/query/struct.Condition.html).\\n\\n```rust\\nuse sea_orm::{entity::*, query::*, tests_cfg::cake, sea_query::Expr, DbBackend};\\n\\nassert_eq!(\\n cake::Entity::find()\\n .filter(\\n Condition::all()\\n .add(\\n Condition::all()\\n .not()\\n .add(Expr::val(1).eq(1))\\n .add(Expr::val(2).eq(2))\\n )\\n .add(\\n Condition::any()\\n .add(Expr::val(3).eq(3))\\n .add(Expr::val(4).eq(4))\\n )\\n )\\n .build(DbBackend::Postgres)\\n .to_string(),\\n r#\\"SELECT \\"cake\\".\\"id\\", \\"cake\\".\\"name\\" FROM \\"cake\\" WHERE (NOT (1 = 1 AND 2 = 2)) AND (3 = 3 OR 4 = 4)\\"#\\n);\\n```\\n\\nContributed by:\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n nitnelave\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n 6xzo\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n## Community\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\nHere is the roadmap for SeaORM [`0.3.x`](https://github.com/SeaQL/sea-orm/milestone/3)."},{"id":"2021-09-20-introducing-sea-orm","metadata":{"permalink":"/blog/2021-09-20-introducing-sea-orm","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-09-20-introducing-sea-orm.md","source":"@site/blog/2021-09-20-introducing-sea-orm.md","title":"Introducing SeaORM \ud83d\udc1a","description":"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.","date":"2021-09-20T00:00:00.000Z","formattedDate":"September 20, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":4.005,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2021-09-20-introducing-sea-orm","title":"Introducing SeaORM \ud83d\udc1a","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"What\'s new in SeaORM 0.2.4","permalink":"/blog/2021-10-01-whats-new-in-0.2.4"},"nextItem":{"title":"Release Model","permalink":"/blog/2021-08-30-release-model"}},"content":"We are pleased to introduce SeaORM [`0.2.2`](https://github.com/SeaQL/sea-orm/releases/tag/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`.\\n\\nRust 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.\\n\\nSeaORM 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.\\n\\nLet\'s have a quick tour of SeaORM.\\n\\n## Async\\n\\nHere is how you\'d execute multiple queries in parallel:\\n\\n```rust\\n// execute multiple queries in parallel\\nlet cakes_and_fruits: (Vec, Vec) =\\n futures::try_join!(Cake::find().all(&db), Fruit::find().all(&db))?;\\n```\\n\\n## Dynamic\\n\\nYou can use SeaQuery to build complex queries without \'fighting the ORM\':\\n\\n```rust\\n// build subquery with ease\\nlet cakes_with_filling: Vec = cake::Entity::find()\\n .filter(\\n Condition::any().add(\\n cake::Column::Id.in_subquery(\\n Query::select()\\n .column(cake_filling::Column::CakeId)\\n .from(cake_filling::Entity)\\n .to_owned(),\\n ),\\n ),\\n )\\n .all(&db)\\n .await?;\\n```\\n\\n[More on SeaQuery](https://docs.rs/sea-query/*/sea_query/)\\n\\n## Testable\\n\\nTo write unit tests, you can use our mock interface:\\n\\n```rust\\n// Setup mock connection\\nlet db = MockDatabase::new(DbBackend::Postgres)\\n .append_query_results(vec![\\n vec![\\n cake::Model {\\n id: 1,\\n name: \\"New York Cheese\\".to_owned(),\\n },\\n ],\\n ])\\n .into_connection();\\n\\n// Perform your application logic\\nassert_eq!(\\n cake::Entity::find().one(&db).await?,\\n Some(cake::Model {\\n id: 1,\\n name: \\"New York Cheese\\".to_owned(),\\n })\\n);\\n\\n// Compare it against the expected transaction log\\nassert_eq!(\\n db.into_transaction_log(),\\n vec![\\n Transaction::from_sql_and_values(\\n DbBackend::Postgres,\\n r#\\"SELECT \\"cake\\".\\"id\\", \\"cake\\".\\"name\\" FROM \\"cake\\" LIMIT $1\\"#,\\n vec![1u64.into()]\\n ),\\n ]\\n);\\n```\\n\\n[More on testing](https://www.sea-ql.org/SeaORM/docs/write-test/mock)\\n\\n## Service Oriented\\n\\nHere is an example `Rocket` handler with pagination:\\n\\n```rust\\n#[get(\\"/?&\\")]\\nasync fn list(\\n conn: Connection,\\n page: Option,\\n per_page: Option,\\n) -> Template {\\n // Set page number and items per page\\n let page = page.unwrap_or(1);\\n let per_page = per_page.unwrap_or(10);\\n\\n // Setup paginator\\n let paginator = Post::find()\\n .order_by_asc(post::Column::Id)\\n .paginate(&conn, per_page);\\n let num_pages = paginator.num_pages().await.unwrap();\\n\\n // Fetch paginated posts\\n let posts = paginator\\n .fetch_page(page - 1)\\n .await\\n .expect(\\"could not retrieve posts\\");\\n\\n Template::render(\\n \\"index\\",\\n context! {\\n page: page,\\n per_page: per_page,\\n posts: posts,\\n num_pages: num_pages,\\n },\\n )\\n}\\n```\\n\\n[Full Rocket example](https://github.com/SeaQL/sea-orm/tree/master/examples/rocket_example)\\n\\nWe are building more examples for other web frameworks too.\\n\\n## People\\n\\nSeaQL is a community driven project. We welcome you to participate, contribute and together build for Rust\'s future.\\n\\n### Core Members\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Chris Tsang\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Billy Chan\\n
\\n
\\n
\\n
\\n
\\n
\\n\\n### Contributors\\n\\nAs a courtesy, here is the list of SeaQL\'s early contributors (in alphabetic order):\\n\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ari Seyhun\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ayomide Bamidele\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Ben Armstead\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Bobby Ng\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Daniel Lyne\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Hirtol\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sylvie Rinner\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Marco Napetti\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Markus Merklinger\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Muhannad\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n nitnelave\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Rapha\xebl Ducha\xeene\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n R\xe9mi Kalbe\\n
\\n
\\n
\\n
\\n
\\n
\\n \\n \\n \\n
\\n
\\n Sam Samai\\n
\\n
\\n
\\n
\\n
\\n
"},{"id":"2021-08-30-release-model","metadata":{"permalink":"/blog/2021-08-30-release-model","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-08-30-release-model.md","source":"@site/blog/2021-08-30-release-model.md","title":"Release Model","description":"Today we will outline our release plan in the near future.","date":"2021-08-30T00:00:00.000Z","formattedDate":"August 30, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":0.85,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2021-08-30-release-model","title":"Release Model","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Introducing SeaORM \ud83d\udc1a","permalink":"/blog/2021-09-20-introducing-sea-orm"},"nextItem":{"title":"Hello World","permalink":"/blog/2021-08-07-hello-world"}},"content":"Today we will outline our release plan in the near future.\\n\\nOne of Rust\'s slogan is [Stability Without Stagnation](https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#stability-without-stagnation), and SeaQL\'s take on it, is \'progression without stagnation\'.\\n\\nBefore 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`.\\n\\nAfter that, a major release will be rolled out every year. So you will probably be expecting a `2.0` in 2023.\\n\\nAll of these is only made possible with a solid infrastructure. While we have a [test suite](https://github.com/SeaQL/sea-orm/actions), 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.\\n\\nWe hope that a rolling release model will provide momentum to the community and propell us forward in the near future."},{"id":"2021-08-07-hello-world","metadata":{"permalink":"/blog/2021-08-07-hello-world","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-08-07-hello-world.md","source":"@site/blog/2021-08-07-hello-world.md","title":"Hello World","description":"After 8 months of secrecy, SeaORM is now public!","date":"2021-08-07T00:00:00.000Z","formattedDate":"August 7, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":0.28,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2021-08-07-hello-world","title":"Hello World","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Release Model","permalink":"/blog/2021-08-30-release-model"},"nextItem":{"title":"Welcome to SeaQL","permalink":"/blog/2021-07-01-welcome"}},"content":"After [8 months of secrecy](https://github.com/SeaQL/sea-query/discussions/9), SeaORM is now public!\\n\\nThe Rust async ecosystem is definitely thriving, with Tokio [announcing Axum](https://tokio.rs/blog/2021-07-announcing-axum) a week before.\\n\\nWe are now busy doing the brush ups to head towards our announcement in Sep.\\n\\nIf you stumbled upon us just now, well, hello! We sincerely invite you to be our alpha tester."},{"id":"2021-07-01-welcome","metadata":{"permalink":"/blog/2021-07-01-welcome","editUrl":"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2021-07-01-welcome.md","source":"@site/blog/2021-07-01-welcome.md","title":"Welcome to SeaQL","description":"One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren\'t many choices. So we have to write our own.","date":"2021-07-01T00:00:00.000Z","formattedDate":"July 1, 2021","tags":[{"label":"news","permalink":"/blog/tags/news"}],"readingTime":0.925,"hasTruncateMarker":false,"authors":[{"name":"Chris Tsang","title":"SeaQL Team","url":"https://github.com/tyt2y3","imageURL":"https://avatars.githubusercontent.com/u/1782664?v=4"}],"frontMatter":{"slug":"2021-07-01-welcome","title":"Welcome to SeaQL","author":"Chris Tsang","author_title":"SeaQL Team","author_url":"https://github.com/tyt2y3","author_image_url":"https://avatars.githubusercontent.com/u/1782664?v=4","tags":["news"]},"prevItem":{"title":"Hello World","permalink":"/blog/2021-08-07-hello-world"}},"content":"One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren\'t many choices. So we have to write our own.\\n\\nDecember last year, we released [SeaQuery](https://github.com/SeaQL/sea-query), and received welcoming responses from the community. We decided to push the project further and develop a full blown async ORM.\\n\\nIt has been a bumpy ride, as designing an async ORM requires working within and sometimes around Rust\'s unique type system. After several iterations of experimentation, I think we\'ve attained a balance between static & dynamic and compile-time & run-time that it offers benefits of the Rust language while still be familiar and easy-to-work-with for those who come from other languages.\\n\\nSeaORM is tentative to be released in Sep 2021 and stabilize in May 2022. We hope that SeaORM will become a go-to choice for working with databases in Rust and that the Rust language will be adopted by more organizations in building applications.\\n\\nIf you are intrigued like I do, please stay in touch and join the community.\\n\\n[Share your thoughts here](https://github.com/SeaQL/seaql.github.io/discussions/3)."}]}')}}]); \ No newline at end of file diff --git a/blog/assets/js/ce29dbb1.eee58e3c.js b/blog/assets/js/ce29dbb1.eee58e3c.js new file mode 100644 index 00000000000..dda23f8c785 --- /dev/null +++ b/blog/assets/js/ce29dbb1.eee58e3c.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunksea_ql_blog=self.webpackChunksea_ql_blog||[]).push([[1861],{9680:(e,a,t)=>{t.d(a,{Zo:()=>p,kt:()=>d});var r=t(6687);function n(e,a,t){return a in e?Object.defineProperty(e,a,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[a]=t,e}function o(e,a){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);a&&(r=r.filter((function(a){return Object.getOwnPropertyDescriptor(e,a).enumerable}))),t.push.apply(t,r)}return t}function i(e){for(var a=1;a=0||(n[t]=e[t]);return n}(e,a);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(n[t]=e[t])}return n}var l=r.createContext({}),m=function(e){var a=r.useContext(l),t=a;return e&&(t="function"==typeof e?e(a):i(i({},a),e)),t},p=function(e){var a=m(e.components);return r.createElement(l.Provider,{value:a},e.children)},c={inlineCode:"code",wrapper:function(e){var a=e.children;return r.createElement(r.Fragment,{},a)}},u=r.forwardRef((function(e,a){var t=e.components,n=e.mdxType,o=e.originalType,l=e.parentName,p=s(e,["components","mdxType","originalType","parentName"]),u=m(t),d=n,h=u["".concat(l,".").concat(d)]||u[d]||c[d]||o;return t?r.createElement(h,i(i({ref:a},p),{},{components:t})):r.createElement(h,i({ref:a},p))}));function d(e,a){var t=arguments,n=a&&a.mdxType;if("string"==typeof e||n){var o=t.length,i=new Array(o);i[0]=u;var s={};for(var l in a)hasOwnProperty.call(a,l)&&(s[l]=a[l]);s.originalType=e,s.mdxType="string"==typeof e?e:n,i[1]=s;for(var m=2;m{t.r(a),t.d(a,{assets:()=>l,contentTitle:()=>i,default:()=>c,frontMatter:()=>o,metadata:()=>s,toc:()=>m});var r=t(1308),n=(t(6687),t(9680));const o={slug:"2024-01-23-whats-new-in-seaorm-0.12.x",title:"What's new in SeaORM 0.12.x",author:"SeaQL Team",author_title:"Chris Tsang",author_url:"https://github.com/SeaQL",author_image_url:"https://www.sea-ql.org/blog/img/SeaQL.png",tags:["news"]},i=void 0,s={permalink:"/blog/2024-01-23-whats-new-in-seaorm-0.12.x",editUrl:"https://github.com/SeaQL/seaql.github.io/edit/master/Blog/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md",source:"@site/blog/2024-01-23-whats-new-in-seaorm-0.12.x.md",title:"What's new in SeaORM 0.12.x",description:"It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!",date:"2024-01-23T00:00:00.000Z",formattedDate:"January 23, 2024",tags:[{label:"news",permalink:"/blog/tags/news"}],readingTime:6.89,hasTruncateMarker:!1,authors:[{name:"SeaQL Team",title:"Chris Tsang",url:"https://github.com/SeaQL",imageURL:"https://www.sea-ql.org/blog/img/SeaQL.png"}],frontMatter:{slug:"2024-01-23-whats-new-in-seaorm-0.12.x",title:"What's new in SeaORM 0.12.x",author:"SeaQL Team",author_title:"Chris Tsang",author_url:"https://github.com/SeaQL",author_image_url:"https://www.sea-ql.org/blog/img/SeaQL.png",tags:["news"]},nextItem:{title:"SeaQL Community Survey 2023 Results",permalink:"/blog/2024-01-18-community-survey-2023"}},l={authorsImageUrls:[void 0]},m=[{value:"Celebrating 2M downloads on crates.io \ud83d\udce6",id:"celebrating-2m-downloads-on-cratesio-",level:2},{value:"New Features",id:"new-features",level:2},{value:"Entity format update",id:"entity-format-update",level:3},{value:"Cursor paginator improvements",id:"cursor-paginator-improvements",level:3},{value:"Added "proxy" to database backend",id:"added-proxy-to-database-backend",level:3},{value:"Enhancements",id:"enhancements",level:2},{value:"Bug fixes",id:"bug-fixes",level:2},{value:"Upgrades",id:"upgrades",level:2},{value:"House Keeping",id:"house-keeping",level:2},{value:"Release planning",id:"release-planning",level:2},{value:"Sponsor",id:"sponsor",level:2},{value:"Gold Sponsors",id:"gold-sponsors",level:4},{value:"Sponsors",id:"sponsors",level:4},{value:"Rustacean Sticker Pack \ud83e\udd80",id:"rustacean-sticker-pack-",level:2}],p={toc:m};function c(e){let{components:a,...t}=e;return(0,n.kt)("wrapper",(0,r.Z)({},p,t,{components:a,mdxType:"MDXLayout"}),(0,n.kt)("img",{alt:"SeaORM 0.12 Banner",src:"/blog/img/SeaORM%2012%20Banner.png"}),(0,n.kt)("p",null,"It had been a while since the initial ",(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/"},"SeaORM 0.12 release"),". This blog post summarizes the new features and enhancements introduced in SeaORM ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/releases/tag/0.12.2"},(0,n.kt)("inlineCode",{parentName:"a"},"0.12.2"))," through ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/releases/tag/0.12.12"},(0,n.kt)("inlineCode",{parentName:"a"},"0.12.12")),"!"),(0,n.kt)("h2",{id:"celebrating-2m-downloads-on-cratesio-"},"Celebrating 2M downloads on crates.io \ud83d\udce6"),(0,n.kt)("p",null,"We've just reached the milestone of 2,000,000 all time downloads on ",(0,n.kt)("a",{parentName:"p",href:"https://crates.io/crates/sea-orm"},"crates.io"),". It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community."),(0,n.kt)("h2",{id:"new-features"},"New Features"),(0,n.kt)("h3",{id:"entity-format-update"},"Entity format update"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1898"},"#1898")," Add support for root JSON arrays (requires the ",(0,n.kt)("inlineCode",{parentName:"li"},"json-array")," / ",(0,n.kt)("inlineCode",{parentName:"li"},"postgres-array")," feature)! It involved an intricate type system refactor to work around the orphan rule.")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "json_struct_vec")]\npub struct Model {\n #[sea_orm(primary_key)]\n pub id: i32,\n #[sea_orm(column_type = "Json")]\n pub struct_vec: Vec,\n}\n\n#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]\npub struct JsonColumn {\n pub value: String,\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2009"},"#2009")," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"comment")," attribute for Entity; ",(0,n.kt)("inlineCode",{parentName:"li"},"create_table_from_entity")," now supports comment on MySQL")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]\n#[sea_orm(table_name = "applog", comment = "app logs")]\npub struct Model {\n #[sea_orm(primary_key, comment = "ID")]\n pub id: i32,\n #[sea_orm(comment = "action")]\n pub action: String,\n pub json: Json,\n pub created_at: DateTimeWithTimeZone,\n}\n')),(0,n.kt)("h3",{id:"cursor-paginator-improvements"},"Cursor paginator improvements"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2037"},"#2037")," Added descending order to Cursor:")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},"// (default behaviour) Before 5 ASC, i.e. id < 5\n\nlet mut cursor = Entity::find().cursor_by(Column::Id);\ncursor.before(5);\n\nassert_eq!(\n cursor.first(4).all(db).await?,\n [\n Model { id: 1 },\n Model { id: 2 },\n Model { id: 3 },\n Model { id: 4 },\n ]\n);\n\n// (new API) After 5 DESC, i.e. id < 5\n\nlet mut cursor = Entity::find().cursor_by(Column::Id);\ncursor.after(5).desc();\n\nassert_eq!(\n cursor.first(4).all(db).await?,\n [\n Model { id: 4 },\n Model { id: 3 },\n Model { id: 2 },\n Model { id: 1 },\n ]\n);\n")),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1826"},"#1826")," Added cursor support to ",(0,n.kt)("inlineCode",{parentName:"li"},"SelectTwo"),":")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},"// Join with linked relation; cursor by first table's id\n\ncake::Entity::find()\n .find_also_linked(entity_linked::CakeToFillingVendor)\n .cursor_by(cake::Column::Id)\n .before(10)\n .first(2)\n .all(&db)\n .await?\n\n// Join with relation; cursor by the 2nd table's id \n\ncake::Entity::find()\n .find_also_related(Fruit)\n .cursor_by_other(fruit::Column::Id)\n .before(10)\n .first(2)\n .all(&db)\n .await?\n")),(0,n.kt)("h3",{id:"added-proxy-to-database-backend"},'Added "proxy" to database backend'),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/pull/1881"},"#1881"),", ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/pull/2000"},"#2000"),' Added "proxy" to database backend (requires feature flag ',(0,n.kt)("inlineCode",{parentName:"p"},"proxy"),")."),(0,n.kt)("p",null,"It enables the possibility of using SeaORM on edge / client-side! See the ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/tree/master/examples/proxy_gluesql_example"},"GlueSQL demo")," for an example."),(0,n.kt)("h2",{id:"enhancements"},"Enhancements"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1954"},"#1954")," ","[sea-orm-macro]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"#[sea_orm(skip)]")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"FromQueryResult")," derive macro")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]\npub struct PublicUser {\n pub id: i64,\n pub name: String,\n #[serde(skip_serializing_if = "Vec::is_empty")]\n #[sea_orm(skip)]\n pub something: Something,\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1598"},"#1598")," ","[sea-orm-macro]"," Added support for Postgres arrays in ",(0,n.kt)("inlineCode",{parentName:"li"},"FromQueryResult")," impl of ",(0,n.kt)("inlineCode",{parentName:"li"},"JsonValue"))),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'// existing API:\n\nassert_eq!(\n Entity::find_by_id(1).one(db).await?,\n Some(Model {\n id: 1,\n name: "Collection 1".into(),\n integers: vec![1, 2, 3],\n teas: vec![Tea::BreakfastTea],\n colors: vec![Color::Black],\n })\n);\n\n// new API:\n\nassert_eq!(\n Entity::find_by_id(1).into_json().one(db).await?,\n Some(json!({\n "id": 1,\n "name": "Collection 1",\n "integers": [1, 2, 3],\n "teas": ["BreakfastTea"],\n "colors": [0],\n }))\n);\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1828"},"#1828")," ","[sea-orm-migration]"," Check if an index exists")),(0,n.kt)("pre",null,(0,n.kt)("code",{parentName:"pre",className:"language-rust"},'use sea_orm_migration::prelude::*;\n#[derive(DeriveMigrationName)]\npub struct Migration;\n#[async_trait::async_trait]\nimpl MigrationTrait for Migration {\n async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {\n // ...\n\n // Make sure the index haven\'t been created\n assert!(!manager.has_index("cake", "cake_name_index").await?);\n\n manager\n .create_index(\n Index::create()\n .name("cake_name_index")\n .table(Cake::Table)\n .col(Cake::Name)\n .to_owned(),\n )\n .await?;\n\n Ok(())\n }\n\n async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {\n // ...\n }\n}\n')),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2030"},"#2030")," Improve query performance of ",(0,n.kt)("inlineCode",{parentName:"li"},"Paginator"),"'s ",(0,n.kt)("inlineCode",{parentName:"li"},"COUNT")," query"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2055"},"#2055")," Added SQLx slow statements logging to ",(0,n.kt)("inlineCode",{parentName:"li"},"ConnectOptions")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1867"},"#1867")," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"QuerySelect::lock_with_behavior")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2002"},"#2002")," Cast enums in ",(0,n.kt)("inlineCode",{parentName:"li"},"is_in")," and ",(0,n.kt)("inlineCode",{parentName:"li"},"is_not_in")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1999"},"#1999")," Add source annotations to errors"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/issues/1960"},"#1960")," Implement ",(0,n.kt)("inlineCode",{parentName:"li"},"StatementBuilder")," for ",(0,n.kt)("inlineCode",{parentName:"li"},"sea_query::WithQuery")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1979"},"#1979")," Added method ",(0,n.kt)("inlineCode",{parentName:"li"},"expr_as_")," that accepts ",(0,n.kt)("inlineCode",{parentName:"li"},"self")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1868"},"#1868")," Loader: use ",(0,n.kt)("inlineCode",{parentName:"li"},"ValueTuple")," as hash key"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1934"},"#1934")," ","[sea-orm-cli]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"--enum-extra-derives")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1952"},"#1952")," ","[sea-orm-cli]"," Added ",(0,n.kt)("inlineCode",{parentName:"li"},"--enum-extra-attributes")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1693"},"#1693")," ","[sea-orm-cli]"," Support generation of related entity with composite foreign key")),(0,n.kt)("h2",{id:"bug-fixes"},"Bug fixes"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1855"},"#1855"),", ",(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2054"},"#2054")," ","[sea-orm-macro]"," Qualify types in ",(0,n.kt)("inlineCode",{parentName:"li"},"DeriveValueType")," macro"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1953"},"#1953")," ","[sea-orm-cli]"," Fix duplicated active enum use statements on generated entities"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1821"},"#1821")," ","[sea-orm-cli]"," Fix entity generation for non-alphanumeric enum variants"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2071"},"#2071")," ","[sea-orm-cli]"," Fix entity generation for relations with composite keys"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/issues/1800"},"#1800")," Fixed ",(0,n.kt)("inlineCode",{parentName:"li"},"find_with_related")," consolidation logic"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/commit/5a6acd67312601e4dba32896600044950e20f99f"},"5a6acd67")," Fixed ",(0,n.kt)("inlineCode",{parentName:"li"},"Loader")," panic on empty inputs")),(0,n.kt)("h2",{id:"upgrades"},"Upgrades"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1984"},"#1984")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"axum")," example to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.7")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1858"},"#1858")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"chrono")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.4.30")),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1959"},"#1959")," Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"rocket")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.5.0")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"sea-query")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.30.5")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"sea-schema")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.14.2")),(0,n.kt)("li",{parentName:"ul"},"Upgraded ",(0,n.kt)("inlineCode",{parentName:"li"},"salvo")," to ",(0,n.kt)("inlineCode",{parentName:"li"},"0.50"))),(0,n.kt)("h2",{id:"house-keeping"},"House Keeping"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/2057"},"#2057")," Fix clippy warnings on 1.75"),(0,n.kt)("li",{parentName:"ul"},(0,n.kt)("a",{parentName:"li",href:"https://github.com/SeaQL/sea-orm/pull/1811"},"#1811")," Added test cases for ",(0,n.kt)("inlineCode",{parentName:"li"},"find_xxx_related/linked"))),(0,n.kt)("h2",{id:"release-planning"},"Release planning"),(0,n.kt)("p",null,"In the ",(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/blog/2023-08-12-announcing-seaorm-0.12/"},"announcement blog post")," of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!"),(0,n.kt)("p",null,"There are still a few breaking changes planned for the next major release. After some ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/SeaQL/sea-orm/discussions/2031"},"discussions")," and consideration, we decided that the next major release will be a release candidate for 1.0!"),(0,n.kt)("h2",{id:"sponsor"},"Sponsor"),(0,n.kt)("p",null,"A big thank to ",(0,n.kt)("a",{parentName:"p",href:"https://www.digitalocean.com/"},"DigitalOcean")," who sponsored our servers, and ",(0,n.kt)("a",{parentName:"p",href:"https://www.jetbrains.com/"},"JetBrains")," who sponsored our IDE, and every sponsor on ",(0,n.kt)("a",{parentName:"p",href:"https://github.com/sponsors/SeaQL"},"GitHub Sponsor"),"!"),(0,n.kt)("p",null,"If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization."),(0,n.kt)("p",null,"A big shout out to our sponsors \ud83d\ude07:"),(0,n.kt)("h4",{id:"gold-sponsors"},"Gold Sponsors"),(0,n.kt)("a",{href:"https://www.digitalocean.com/"},(0,n.kt)("img",{src:"https://www.sea-ql.org/static/sponsors/Osmos.svg#light",width:"238"}),(0,n.kt)("img",{src:"https://www.sea-ql.org/static/sponsors/Osmos-dark.svg#dark",width:"238"})),(0,n.kt)("h4",{id:"sponsors"},"Sponsors"),(0,n.kt)("div",{class:"row"},(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Sytten"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/2366731?u=2f43900772265deac96eb7a816d28a5a48b9a8dd&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"\xc9mile Fugulin")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/tugascript"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/64930104?u=1171ed4ccb6da73b52de274109077686290da3a5&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Afonso Barracha")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/shanesveller"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/831?u=474c7b31ddf0a5c1a03d1142dd18a300279c644a&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Shane Sveller")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/deansheather"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/11241812?u=260538c7d8b8c3c5350dba175ebb8294358441e0&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Dean Sheather")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/marcusbuffett"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/1834328?u=fd066d99cf4a6333bfb3927d1c756af4bb8baf7e&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Marcus Buffett")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/reneklacan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/1935686?u=132be985351312fcf96999daef515f551a93bb0d&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Ren\xe9 Kla\u010dan")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Iceapinan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/2698243?u=a852c75ac10098b9980f57af298be1399f6de66b&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"IceApinan")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/trueb2"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/8592049?u=031c9ee96b47c27e3a8c485c3c0ebcd4f96120c9&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Jacob Trueb")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/ktanaka101"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/10344925?u=a96d92e7cdd73f774b35fd0bc998964c07b24e29&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Kentaro Tanaka")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/siketyan"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/12772118?u=1a51e0a06690e52982e7594bc7379481e65155a1&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Natsuki Ikeguchi")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/mmuellersoppart"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/16762461?u=bef7454cb73c164b2d18e077e5ba6b7891aae3d2&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Marlon Mueller-Soppart")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/gitmalong"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/18363591?v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"ul")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/manfredcml"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/27536502?u=b71636bdabbc698458b32e2ac05c5771ad41097e&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Manfred Lee")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/kallydev"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/36319157?u=5be882aa4dbe7eea97b1a80a6473857369146df6&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"KallyDev")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/dsgallups"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/44790295?u=d1c8d2a60930dfbe95497df7fecf52cf5d95dd5f&v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Daniel Gallups")))),(0,n.kt)("div",{class:"col col--6 margin-bottom--md"},(0,n.kt)("div",{class:"avatar"},(0,n.kt)("a",{class:"avatar__photo-link avatar__photo avatar__photo--sm",href:"https://github.com/Coolpany-SE"},(0,n.kt)("img",{src:"https://avatars.githubusercontent.com/u/96304487?v=4"})),(0,n.kt)("div",{class:"avatar__intro"},(0,n.kt)("div",{class:"avatar__name"},"Coolpany-SE"))))),(0,n.kt)("h2",{id:"rustacean-sticker-pack-"},"Rustacean Sticker Pack \ud83e\udd80"),(0,n.kt)("p",null,"The Rustacean Sticker Pack is the perfect way to express your passion for Rust.\nOur stickers are made with a premium water-resistant vinyl with a unique matte finish.\nStick them on your laptop, notebook, or any gadget to show off your love for Rust!"),(0,n.kt)("p",null,"Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects."),(0,n.kt)("p",null,"Sticker Pack Contents:"),(0,n.kt)("ul",null,(0,n.kt)("li",{parentName:"ul"},"Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG"),(0,n.kt)("li",{parentName:"ul"},"Mascot of SeaQL: Terres the Hermit Crab"),(0,n.kt)("li",{parentName:"ul"},"Mascot of Rust: Ferris the Crab"),(0,n.kt)("li",{parentName:"ul"},"The Rustacean word")),(0,n.kt)("p",null,(0,n.kt)("a",{parentName:"p",href:"https://www.sea-ql.org/sticker-pack/"},"Support SeaQL and get a Sticker Pack!")),(0,n.kt)("a",{href:"https://www.sea-ql.org/sticker-pack/"},(0,n.kt)("img",{style:{borderRadius:"25px"},alt:"Rustacean Sticker Pack by SeaQL",src:"https://www.sea-ql.org/static/sticker-pack-1s.jpg"})))}c.isMDXComponent=!0}}]); \ No newline at end of file diff --git a/blog/assets/js/runtime~main.830afb7b.js b/blog/assets/js/runtime~main.830afb7b.js new file mode 100644 index 00000000000..38336aa3b26 --- /dev/null +++ b/blog/assets/js/runtime~main.830afb7b.js @@ -0,0 +1 @@ +(()=>{"use strict";var e,c,a,b,f,d={},t={};function r(e){var c=t[e];if(void 0!==c)return c.exports;var a=t[e]={exports:{}};return d[e].call(a.exports,a,a.exports,r),a.exports}r.m=d,e=[],r.O=(c,a,b,f)=>{if(!a){var d=1/0;for(i=0;i=f)&&Object.keys(r.O).every((e=>r.O[e](a[o])))?a.splice(o--,1):(t=!1,f0&&e[i-1][2]>f;i--)e[i]=e[i-1];e[i]=[a,b,f]},r.n=e=>{var c=e&&e.__esModule?()=>e.default:()=>e;return r.d(c,{a:c}),c},a=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,r.t=function(e,b){if(1&b&&(e=this(e)),8&b)return e;if("object"==typeof e&&e){if(4&b&&e.__esModule)return e;if(16&b&&"function"==typeof e.then)return e}var f=Object.create(null);r.r(f);var d={};c=c||[null,a({}),a([]),a(a)];for(var t=2&b&&e;"object"==typeof t&&!~c.indexOf(t);t=a(t))Object.getOwnPropertyNames(t).forEach((c=>d[c]=()=>e[c]));return d.default=()=>e,r.d(f,d),f},r.d=(e,c)=>{for(var a in c)r.o(c,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:c[a]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce(((c,a)=>(r.f[a](e,c),c)),[])),r.u=e=>"assets/js/"+({460:"44a37373",470:"f81c7501",555:"3a016d32",625:"ad301195",728:"355c89fa",757:"16a359ff",1119:"422ce757",1143:"ec43586b",1158:"8e70e332",1195:"8d231b22",1244:"41493765",1477:"b2f554cd",1699:"7dc96989",1713:"a7023ddc",1861:"ce29dbb1",1976:"2baf8dba",2101:"b97c52d6",2257:"3f43064b",2266:"188fe170",2292:"7f0332d4",2294:"1f20fd32",2390:"0167bfd0",2468:"201fdcf7",2535:"814f3328",2661:"88b9a348",2798:"e3ae061a",2834:"8e7ce6b1",3011:"44176adc",3085:"1f391b9e",3089:"a6aa9e1f",3120:"17357cc0",3172:"006a681b",3201:"b9583239",3209:"9d9eec25",3215:"5b929ea3",3235:"e8af6440",3238:"018944f3",3608:"9e4087bc",3626:"50e480a9",3662:"63ab7bee",3674:"c56dd5e8",3765:"906aa06c",3821:"e19d4a34",3846:"043bb92b",3858:"15544a9f",3962:"f30200a6",4013:"01a85c17",4024:"65be33d9",4137:"6619049b",4283:"ccee9b10",4977:"683671ef",5004:"cfde3c28",5031:"9ab4baf6",5053:"e6d1732d",5076:"90eac680",5504:"c14b0d75",5545:"1d787708",5561:"c835dddf",5690:"6ee93572",5991:"7d7fd0cd",6058:"0545335c",6103:"ccc49370",6221:"b11bad44",6322:"03d1c922",6329:"eef5bdc5",6383:"dc50f154",6545:"e51a6fc7",6554:"9d602b09",6595:"7f6c9512",6685:"4eafb24f",6823:"56cb9860",6880:"cb7f7ab4",7471:"ed9634b1",7536:"d590458b",7541:"5667b71b",7920:"1a4e3797",7958:"c35cbffa",8055:"f19630b7",8122:"404b6aed",8193:"97592684",8354:"59179933",8610:"6875c492",8731:"5d22bf25",8774:"d33a7941",9067:"4cd8356f",9279:"f8b9fff6",9305:"94887e63",9660:"66e4b60c",9679:"79e21966",9704:"191ad80f",9788:"25b1a681"}[e]||e)+"."+{402:"3ec6ec7f",460:"a4733fd4",470:"70097375",555:"12b4deca",625:"c62fca54",728:"44f86885",757:"ec032b92",1119:"6415f4a2",1143:"efcadf62",1158:"89f321c1",1195:"bc0022b4",1244:"1e566919",1477:"93bd73b0",1699:"b2664b4e",1713:"4a3d8947",1861:"eee58e3c",1976:"4290cca1",2101:"f7ab964b",2257:"79c87834",2266:"179aad07",2292:"883139c4",2294:"740a8399",2390:"1de75629",2468:"d4222d16",2535:"766be991",2661:"7a69f1a7",2798:"40814715",2834:"304fcfa8",3011:"3ac93ae8",3085:"a85e9037",3089:"2d95a074",3120:"5b463544",3172:"20083386",3201:"35c33790",3209:"be3c8124",3215:"92f510dc",3235:"3db95050",3238:"f139cfc2",3402:"ddf4240f",3608:"2f904ac0",3626:"208a34eb",3662:"970dc07f",3674:"30a55308",3765:"a6eb5ac3",3821:"f82e8027",3846:"feb5e7c7",3858:"4419a9ac",3962:"7b606668",4013:"591d055a",4024:"d6d7d6fa",4137:"29cd7b70",4283:"3f809fa9",4977:"a0a2d9d1",5004:"16913234",5031:"7ffce016",5053:"4c381839",5076:"626d2a7d",5392:"60a24506",5428:"873f91bd",5504:"dc0b26b5",5507:"5073a2c2",5545:"d7459ca3",5561:"09f10c06",5690:"101bdf21",5991:"fb9b5053",6058:"8a32a995",6103:"1446f44b",6221:"58a548fb",6322:"265d9bc1",6329:"c823ae06",6383:"8328fb54",6545:"1b151fe6",6554:"19deec08",6595:"375bc284",6685:"f15e1f5c",6823:"36ff437e",6880:"b02d2272",7471:"c52cd618",7536:"4ad755b0",7541:"af1e8c26",7920:"fce32664",7958:"d805cd50",8055:"3025ca11",8122:"46ed70cd",8193:"a164a4d8",8354:"43d738ab",8447:"3f0ce5ab",8610:"38a3df43",8687:"255f6528",8731:"cf983875",8774:"3e364973",9067:"55eeb566",9279:"ed61deed",9305:"dd01e81f",9660:"14ea5931",9679:"5e53c0f5",9704:"fbf4a07a",9788:"0add0174"}[e]+".js",r.miniCssF=e=>{},r.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),r.o=(e,c)=>Object.prototype.hasOwnProperty.call(e,c),b={},f="sea-ql-blog:",r.l=(e,c,a,d)=>{if(b[e])b[e].push(c);else{var t,o;if(void 0!==a)for(var n=document.getElementsByTagName("script"),i=0;i{t.onerror=t.onload=null,clearTimeout(s);var f=b[e];if(delete b[e],t.parentNode&&t.parentNode.removeChild(t),f&&f.forEach((e=>e(a))),c)return c(a)},s=setTimeout(u.bind(null,void 0,{type:"timeout",target:t}),12e4);t.onerror=u.bind(null,t.onerror),t.onload=u.bind(null,t.onload),o&&document.head.appendChild(t)}},r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.p="/blog/",r.gca=function(e){return e={41493765:"1244",59179933:"8354",97592684:"8193","44a37373":"460",f81c7501:"470","3a016d32":"555",ad301195:"625","355c89fa":"728","16a359ff":"757","422ce757":"1119",ec43586b:"1143","8e70e332":"1158","8d231b22":"1195",b2f554cd:"1477","7dc96989":"1699",a7023ddc:"1713",ce29dbb1:"1861","2baf8dba":"1976",b97c52d6:"2101","3f43064b":"2257","188fe170":"2266","7f0332d4":"2292","1f20fd32":"2294","0167bfd0":"2390","201fdcf7":"2468","814f3328":"2535","88b9a348":"2661",e3ae061a:"2798","8e7ce6b1":"2834","44176adc":"3011","1f391b9e":"3085",a6aa9e1f:"3089","17357cc0":"3120","006a681b":"3172",b9583239:"3201","9d9eec25":"3209","5b929ea3":"3215",e8af6440:"3235","018944f3":"3238","9e4087bc":"3608","50e480a9":"3626","63ab7bee":"3662",c56dd5e8:"3674","906aa06c":"3765",e19d4a34:"3821","043bb92b":"3846","15544a9f":"3858",f30200a6:"3962","01a85c17":"4013","65be33d9":"4024","6619049b":"4137",ccee9b10:"4283","683671ef":"4977",cfde3c28:"5004","9ab4baf6":"5031",e6d1732d:"5053","90eac680":"5076",c14b0d75:"5504","1d787708":"5545",c835dddf:"5561","6ee93572":"5690","7d7fd0cd":"5991","0545335c":"6058",ccc49370:"6103",b11bad44:"6221","03d1c922":"6322",eef5bdc5:"6329",dc50f154:"6383",e51a6fc7:"6545","9d602b09":"6554","7f6c9512":"6595","4eafb24f":"6685","56cb9860":"6823",cb7f7ab4:"6880",ed9634b1:"7471",d590458b:"7536","5667b71b":"7541","1a4e3797":"7920",c35cbffa:"7958",f19630b7:"8055","404b6aed":"8122","6875c492":"8610","5d22bf25":"8731",d33a7941:"8774","4cd8356f":"9067",f8b9fff6:"9279","94887e63":"9305","66e4b60c":"9660","79e21966":"9679","191ad80f":"9704","25b1a681":"9788"}[e]||e,r.p+r.u(e)},(()=>{var e={1303:0,532:0};r.f.j=(c,a)=>{var b=r.o(e,c)?e[c]:void 0;if(0!==b)if(b)a.push(b[2]);else if(/^(1303|532)$/.test(c))e[c]=0;else{var f=new Promise(((a,f)=>b=e[c]=[a,f]));a.push(b[2]=f);var d=r.p+r.u(c),t=new Error;r.l(d,(a=>{if(r.o(e,c)&&(0!==(b=e[c])&&(e[c]=void 0),b)){var f=a&&("load"===a.type?"missing":a.type),d=a&&a.target&&a.target.src;t.message="Loading chunk "+c+" failed.\n("+f+": "+d+")",t.name="ChunkLoadError",t.type=f,t.request=d,b[1](t)}}),"chunk-"+c,c)}},r.O.j=c=>0===e[c];var c=(c,a)=>{var b,f,d=a[0],t=a[1],o=a[2],n=0;if(d.some((c=>0!==e[c]))){for(b in t)r.o(t,b)&&(r.m[b]=t[b]);if(o)var i=o(r)}for(c&&c(a);n 2024-01-23T00:00:00.000Z -

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. +

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL]]>
diff --git a/blog/gsoc-2022/index.html b/blog/gsoc-2022/index.html index 51988e29d55..ca9306b4d4c 100644 --- a/blog/gsoc-2022/index.html +++ b/blog/gsoc-2022/index.html @@ -10,13 +10,13 @@ - +

Google Summer of Code 2022

ยท 2 min read
SeaQL Team

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

- + \ No newline at end of file diff --git a/blog/index.html b/blog/index.html index 25aa507c5c6..c58981637e2 100644 --- a/blog/index.html +++ b/blog/index.html @@ -10,12 +10,12 @@ - +
-

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. +

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL

ยท 10 min read
SeaQL Team

524 members of the SeaQL community from 41 countries kindly contributed their thoughts on using SeaQL libraries, learning Rust and employing Rust in their day to day development lives. From these responses we hope to get an understanding of where the SeaQL and Rust community stands in 2023.

This is our first community survey, we will conduct the survey annually to keep track of how the community evolves over time.

Demographicsโ€‹

Q. Where are you located in?โ€‹

Participants are from 41 countries across the world!

Other: ArgentinaAustraliaAustriaBelarusBelgiumCyprusCzechiaDenmarkHungaryIranIrelandItalyJapanKazakstanKoreaMongoliaNigeriaNorwayPeruPolandSlovakiaSouth AfricaSpainSwedenTaiwan ThailandTurkeyUkraine

Use of SeaQL Librariesโ€‹

Q. Are you using SeaQL libraries in building a project?โ€‹

Q. Which SeaQL libraries are you using in building a project?โ€‹

Other: SeaographySeaStreamer

Q. Are you using SeaQL libraries in a personal, academic or professional context?โ€‹

Q. Why did you choose SeaQL libraries?โ€‹

Other: Async support, future proof and good documentationGood Query PerformanceIt was recommended on websites and YouTubeDoes not use SQL for migrationsBeginner-friendly and easy to get startedEasy to translate from Eloquent ORM knowledgeCan drop in to SeaQuery if necessaryI started with SQLx, then tried SeaQueryI found good examples on YouTube

Q. What qualities of SeaQL libraries do you think are important?โ€‹

Other: Simple SyntaxBeing able to easily express what you would otherwise be able to write in pure SQLMigration and entity generationClarify of the implementation and usage patternsEfficient query building especially with relations and joinsErgonomic API

Team & Project Natureโ€‹

Q. How many team members (including you) are working on the project?โ€‹

Q. Can you categorize the nature of the project?โ€‹

Other: ForecastingFinancial tradingEnterprise Resource Planning (ERP)FintechCloud infrstructure automationBackend for desktop, mobile and web application

Tech Stackโ€‹

Q. What is your development environment?โ€‹

Linux Breakdownโ€‹

Windows Breakdownโ€‹

macOS Breakdownโ€‹

Q. Which database(s) do you use?โ€‹

Q. Which web framework are you using?โ€‹

Q. What is the deployment environment?โ€‹

Rust at Workโ€‹

Q. Are you using Rust at work?โ€‹

Q. Which industry your company is in?โ€‹

Vague description of the companyโ€‹

A banking companyA business to business lending platformA cloud StorageA consulting companyA cybersecurity management platformAn IT solution companyAn E-Commerce clothing storeA children entertainmets companyA factory construction management platformA fintech startupA geology technology companyA publicly traded health-tech companyA private restaurant chainAn industrial IoT for heating and water distributionsAn internet providerA nonprofit tech research organizationA payment service providerA road intelligence companyA SaaS startupA server hosting providerA DevOps platform that helps our users scale their Kubernetes applicationAn Automotive company

Q. What is the size of your company?โ€‹

Q. How many engineers in your company are dedicated to writing Rust?โ€‹

Q. Which layer(s) of the technology stack are using Rust?โ€‹

Learning Rustโ€‹

Q. Are you learning / new to Rust?โ€‹

Q. Which language(s) are you most familiar with?โ€‹

Q. Are you familiar with SQL?โ€‹

Q. Do you find Rust easy or hard to learn?โ€‹

Q. What motivates you to learn Rust?โ€‹

Other: Ability to develop fast, secure and standalone API driven toolsEfficiency, safety, low resource usageGood design decisions from the startReliability and ease of developmentSchool makes me to learnRust is too coolThe ecosystem of libraries + general competence of lib authorsIt is the most loved languageThe guarantees Rust providesLearning something newType safety and speedWant to get away from NULLNo boilerplate, if you do not want itPerformance

Q. What learning resources do you rely on?โ€‹

Other: YouTubeOnline CoursesChatGPT

Q. What is your first project built using Rust?โ€‹

Other: ChatbotScraperRasterization of the mandelbrot setIoTLibrary

What's Nextโ€‹

Q. Which aspects do you want to see advancement on SeaORM?โ€‹

Thank you for all the suggestions, we will certainly take them into account!

Other: Full MySQL coverageMS SQL Server supportStructured queries for complex joinsA stable releaseData seedingMigrations based on Entity diffsType safetySupport tables without primary keyTurso integrationFetching nested structuresViews

Q. What tools would you be interested in using, if developed first-party by SeaQL?โ€‹

Other: An API integration testing utilityAn oso-based authorization integrationA visual tool for managing migrationsDatabase layout editor (like dbdiagram.io)

Share Your Thoughtsโ€‹

Q. Anything else you want to say?โ€‹

Didn't expect this section to turn into a testimonial, thank you for all the kind words :)

Good job yall

Great projects, thanks for your hard work

I expect it to be an asynchronous type-safe library. Keep up the good work!

I'd like to see entity generation without a database

The website, support from JetBrains, the documentation and the release cycle are very nice!

I'm very interested in how SeaORM will continue evolving and I would like to wish you the best of luck!

I've found SeaORM very useful and I'm very grateful to the development team for creating and maintaining it!

In TypeORM I can write entities and then generate migration from them. It's very handy. It helps to increase development speed. It would be nice to have this functionality in SeaORM.

It needs to have better integration with SeaQuery, I sometimes need to get to it because not all features are available in SeaORM which makes it a pain.

Keep the good work!

Keep going! Love SeaORM!

Keep up the great work. Rust needs a fast, ergonomic and reliable ORM.

SeaORM is very powerful, but the rust docs and tutorial examples could be more fleshed out.

SeaORM is an awesome library. Most things are quite concise and therefore straightforward. Simply a few edge cases concerning DB specific types and values could be better.

The trait system is too complex and coding experience is not pretty well with that.

Automatic migration generation would make the library pretty much perfect in my opinion.

SeaQL tutorials could be better. Much more detailed explanation and definitely it has to have best practices section for Design Patterns like and good best practices related with clean architecture.

SeaQL are great products and itโ€™s very enjoyable using them

Thank you <3

Thank you for awesome library!

Thank you for this wonderful project. I feel the documentation lacks examples for small functions and usage of some obscure features.

Thank you for your hard work!

Thank you for your work on SeaQL, your efforts are appreciated

Thank you for your work, we are seeking actively to include SeaORM in our projects

Thank you very much for your work!

Thanks a lot for the amazing work you guys put into this set of libraries. This is an amazing development for the rust ecosystem.

Thanks and keep up the good work.

Thanks for a great tool!

Thanks for all the amazing work.

Thanks for making SeaORM!

The project I am doing for work is only a prototype, it's a new implementation of a current Python forecasting project which uses a pandas and a custom psycopg2 orm. My intent is to create a faster/dev friendly version with SeaORM and Polars. I am hoping to eventually get a prototype I can display to my team to get a go ahead to fully develop a new version, and to migrate 4-5 other forecasting apps using shared libraries for io and calculations.

I have also been using SeaORM for a small API client for financial data, which I may make open source.

I think one thing which could really improve SeaORM is some more advance examples in the documentation section. The docs are really detailed as far as rust documentation goes.

Very promising project, keep it up.

Thank you so much for taking it upon yourselves to selflessly give your free time. It probably doesn't matter much, but thank you so much for your work. SeaORM is a fantastic tool that I can see myself using for a long time to come. I hope to make contributions in any form when I am under better circumstances :3 Kudos to the team!

ไฝ ไปฌ็š„ๅบ“้žๅธธ็š„ๆฃ’๏ผŒ่‡ณๅฐ‘ๆˆ‘่ง‰ๅพ—ๆฏ”Dieselๅฅฝๅคชๅคšไบ†๏ผŒๅ…ฅ้—จ็ฎ€ๅ•๏ผŒๅฏนๆ–ฐๆ‰‹้žๅธธๅ‹ๅฅฝ๏ผŒ่ฟ™ๆ˜ฏๆœ€ๅคง็š„ไบฎ็‚น๏ผŒๅ…ถๆฌกๆ˜ฏๅบ“่ฒŒไผผๅฏไปฅๅฎž็Žฐๅพˆๅคๆ‚็š„Join SQL้€ป่พ‘่€Œไธ็”จๅ†™ๅŽŸ็”Ÿ็š„SQL๏ผŒ่ฟ™็‚นไนŸๆ˜ฏ้žๅธธๅ€ผๅพ—็‚น่ตž็š„๏ผŒไฝ†ๆ˜ฏๅœจ่ฟ™ๅ—็š„ๆ–‡ๆกฃ่ฒŒไผผๅ†™็š„ๆœ‰็‚น็ฎ€็•ฅไบ†๏ผŒๅธŒๆœ›ๅฏไปฅไธฐๅฏŒไธ€ไธ‹ๆ–‡ๆกฃๅ†…ๅฎน๏ผŒๅฏนไบŽๅคๆ‚ๆŸฅ่ฏข็š„่ฏดๆ˜Žๅฏไปฅๆ›ดๅŠ ่ฏฆ็ป†ไธ€ไบ›๏ผŒ่ฟ™ๆ ทๅฐฑๅ†ๅฅฝไธ่ฟ‡ไบ†ใ€‚่ฐข่ฐขไฝ ไปฌ๏ผŒๆˆ‘ไผšๆŒ็ปญๅ…ณๆณจไฝ ไปฌ๏ผŒๆœชๆฅ็š„้กน็›ฎๅฆ‚ๆžœๆถ‰ๅŠORM๏ผŒ้‚ฃ็ปๅฏน้žไฝ ไปฌ่Žซๅฑžไบ†๏ผ

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. @@ -23,7 +23,7 @@ Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL

ยท One min read
SeaQL Team

It is our honour to have been awarded by OpenUK for the 2023 Award in the Software category! The award ceremony was a very memorable experience. A huge thanks to Red Badger who sponsored the software award.

In 2023, we released SeaStreamer, two major versions of SeaORM, a new version of Seaography, and have been busy working on a new project on the side.

We reached the milestone of 5k GitHub stars and 2M crates.io downloads mid-year.

In the summer, we took in two interns for our 3rd summer of code.

We plan to offer internships tailored to UK students in 2024 through university internship programs. As always, we welcome contributors from all over the world, and may be we will enrol on GSoC 2024 again. (but open-source is not bounded any schedule, so you can start contributing anytime)

A big thanks to our sponsors who continued to support us, and we look forward to a more impactful 2024.

ยท 4 min read
Chris Tsang

If you are writing an async application in Rust, at some point you'd want to separate the code into several crates. There are some benefits:

  1. Better encapsulation. Having a crate boundary between sub-systems can lead to cleaner code and a more well-defined API. No more pub(crate)!
  2. Faster compilation. By breaking down a big crate into several independent small crates, they can be compiled concurrently.

But the question is, if you are using only one async runtime anyway, what are the benefits of writing async-runtime-generic libraries?

  1. Portability. You can easily switch to a different async runtime, or wasm.
  2. Correctness. Testing a library against both tokio and async-std can uncover more bugs, including concurrency bugs (due to fuzzy task execution orders) and "undefined behaviour" either due to misunderstanding or async-runtime implementation details

So now you've decided to write async-runtime-generic libraries! Here I want to share 3 strategies along with examples found in the Rust ecosystem.

Approach 1: Defining your own AsyncRuntime traitโ€‹

Using the futures crate you can write very generic library code, but there is one missing piece: time - to sleep or timeout, you have to rely on an async runtime. If that's all you need, you can define your own AsyncRuntime trait and requires downstream to implement it. This is the approach used by rdkafka:

pub trait AsyncRuntime: Send + Sync + 'static {
type Delay: Future<Output = ()> + Send;

/// It basically means the return value must be a `Future`
fn sleep(duration: Duration) -> Self::Delay;
}

Here is how it's implemented:

impl AsyncRuntime for TokioRuntime {
type Delay = tokio::time::Sleep;

fn sleep(duration: Duration) -> Self::Delay {
tokio::time::sleep(duration)
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>() {
R::sleep(Duration::from_millis(1)).await;
}

Approach 2: Abstract the async runtimes internally and expose feature flagsโ€‹

This is the approach used by redis-rs.

To work with network connections or file handle, you can use the AsyncRead / AsyncWrite traits:

#[async_trait]
pub(crate) trait AsyncRuntime: Send + Sync + 'static {
type Connection: AsyncRead + AsyncWrite + Send + Sync + 'static;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection>;
}

Then you'll define a module for each async runtime:

#[cfg(feature = "runtime-async-std")]
mod async_std_impl;
#[cfg(feature = "runtime-async-std")]
use async_std_impl::*;

#[cfg(feature = "runtime-tokio")]
mod tokio_impl;
#[cfg(feature = "runtime-tokio")]
use tokio_impl::*;

Where each module would look like:

tokio_impl.rs
#[async_trait]
impl AsyncRuntime for TokioRuntime {
type Connection = tokio::net::TcpStream;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection> {
tokio::net::TcpStream::connect(addr).await
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>(conn: R::Connection) {
conn.write(b"some bytes").await;
}

Approach 3: Maintain an async runtime abstraction crateโ€‹

This is the approach used by SQLx and SeaStreamer.

Basically, aggregate all async runtime APIs you'd use and write a wrapper library. This may be tedious, but this also has the benefit of specifying all interactions with the async runtime in one place for your project, which could be handy for debugging or tracing.

For example, async Task handling:

common-async-runtime/tokio_task.rs
pub use tokio::task::{JoinHandle as TaskHandle};

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
tokio::task::spawn(future)
}

async-std's task API is slightly different (in tokio the output is Result<T, JoinError>), which requires some boilerplate:

common-async-runtime/async_std_task.rs
/// A shim to match tokio's API
pub struct TaskHandle<T>(async_std::task::JoinHandle<T>);

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
TaskHandle(async_std::task::spawn(future))
}

#[derive(Debug)]
pub struct JoinError;

impl std::error::Error for JoinError {}

// This is basically how you wrap a `Future`
impl<T> Future for TaskHandle<T> {
type Output = Result<T, JoinError>;

fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.0.poll_unpin(cx) {
std::task::Poll::Ready(res) => std::task::Poll::Ready(Ok(res)),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}

In the library's Cargo.toml, you can simply include common-async-runtime as dependency. This makes your library code 'pure', because now selecting an async runtime is controlled by downstream. Similar to approach 1, this crate can be compiled without any async runtime, which is neat!

Conclusionโ€‹

Happy hacking! Welcome to share your experience with the community.

ยท 5 min read
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaStreamer 0.3.x!

File Backendโ€‹

A major addition in SeaStreamer 0.3 is the file backend. It implements the same high-level MPMC API, enabling streaming to and from files. There are different use cases. For example, it can be used to dump data from Redis / Kafka and process them locally, or as an intermediate file format for storage or transport.

The SeaStreamer File format, .ss is pretty simple. It's very much like .ndjson, but binary. The file format is designed with the following goals:

  1. Binary data support without encoding overheads
  2. Efficiency in rewinding / seeking through a large dump
  3. Streaming-friendliness - File can be truncated without losing integrity

Let me explain in details.

First of all, SeaStreamer File is a container format. It only concerns the message stream and framing, not the payload. It's designed to be paired with a binary message format like Protobuf or BSON.

Encode-freeโ€‹

JSON and CSV are great plain text file formats, but they are not binary friendly. Usually, to encode binary data, one would use base64. It therefore imposes an expensive encoding / decoding overhead. In a binary protocol, delimiters are frequently used to signal message boundaries. As a consequence, byte stuffing is needed to escape the bytes.

In SeaStreamer, we want to avoid the encoding overhead entirely. The payload should be written to disk verbatim. So the file format revolves around constructing message frames and placing checksums to ensure that data is interpreted correctly.

Efficient seekโ€‹

A delimiter-based protocol has an advantage: the byte stream can be randomly sought, and we always have no trouble reading the next message.

Since SeaStreamer does not rely on delimiters, we can't easily align to message frames after a random seek. We solve this problem by placing beacons in a regular interval at fixed locations throughout the file. E.g. say the beacon interval is 1024, there will be a beacon at the 1024th byte, the 2048th, and so on. Then, every time we want to seek to a random location, we'd seek to the closest N * 1024 byte and read from there.

These beacons also double as indices: they contain summaries of the individual streams. So given a particular stream key and sequence number (or timestamp) to search for, SeaStreamer can quickly locate the message just by reading the beacons. It doesn't matter if the stream's messages are sparse!

Streaming-friendlinessโ€‹

It should always be safe to truncate files. It should be relatively easy to split a file into chunks. We should be able to tell if the data is corrupted.

SeaStreamer achieves this by computing a checksum for every message, and also the running checksum of the checksums for each stream. It's not enforced right now, but in theory we can detect if any messages are missing from a stream.

Summaryโ€‹

This file format is also easy to implement in different languages, as we just made an (experimental) reader in Typescript.

That's it! If you are interested, you can go and take a look at the format description.

Redis Backendโ€‹

Redis Streams are underrated! They have high throughput and concurrency, and are best suited for non-persistent stream processing near or on the same host as the application.

The obstacle is probably in library support. Redis Streams' API is rather low level, and there aren't many high-level libraries to help with programming, as opposed to Kafka, which has versatile official programming libraries.

The pitfall is, it's not easy to maximize concurrency with the raw Redis API. To start, you'd need to pipeline XADD commands. You'd also need to time and batch XACKs so that it does not block reads and computation. And of course you want to separate the reads and writes on different threads.

SeaStreamer breaks these obstacles for you and offers a Kafka-like API experience!

Benchmarkโ€‹

In 0.3, we have done some optimizations to improve the throughput of the Redis and File backend. We set our initial benchmark at 100k messages per second, which hopefully we can further improve over time.

Our micro benchmark involves a simple program producing or consuming 100k messages, where each message has a payload of 256 bytes.

For Redis, it's running on the same computer in Docker. On my not-very-impressive laptop with a 10th Gen Intel Core i7, the numbers are somewhat around:

Producerโ€‹

redis    0.5s
stdio 0.5s
file 0.5s

Consumerโ€‹

redis    1.0s
stdio 1.0s
file 1.1s

It practically means that we are comfortably in the realm of producing 100k messages per second, but are just about able to consume 100k messages in 1 second. Suggestions to performance improvements are welcome!

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.

ยท 8 min read
SeaQL Team
SeaORM 0.12 Banner

๐ŸŽ‰ We are pleased to announce SeaORM 0.12 today!

We still remember the time when we first introduced SeaORM to the Rust community two years ago. We set out a goal to enable developers to build asynchronous database-driven applications in Rust.

Today, many open-source projects, a handful of startups and many more closed-source projects are using SeaORM. Thank you all who participated and contributed in the making!

SeaORM Star History

New Features ๐ŸŒŸโ€‹

๐Ÿงญ Seaography: GraphQL integration (preview)โ€‹

Seaography example

Seaography is a GraphQL framework built on top of SeaORM. In 0.12, Seaography integration is built into sea-orm. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a GraphQL server from SeaORM entities!

While Seaography development is still in an early stage, it is especially useful in prototyping and building internal-use admin panels.

Read the documentation to learn more.

Added macro DerivePartialModelโ€‹

#1597 Now you can easily perform custom select to query only the columns you needed

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "Cake")]
struct PartialCake {
name: String,
#[sea_orm(
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))"#
)]
name_upper: String,
}

assert_eq!(
cake::Entity::find()
.into_partial_model::<PartialCake>()
.into_statement(DbBackend::Sqlite)
.to_string(),
r#"SELECT "cake"."name", UPPER("cake"."name") AS "name_upper" FROM "cake""#
);

Added Select::find_with_linkedโ€‹

#1728, #1743 Similar to find_with_related, you can now select related entities and consolidate the models.

// Consider the following link
pub struct BakedForCustomer;

impl Linked for BakedForCustomer {
type FromEntity = Entity;

type ToEntity = super::customer::Entity;

fn link(&self) -> Vec<RelationDef> {
vec![
super::cakes_bakers::Relation::Baker.def().rev(),
super::cakes_bakers::Relation::Cake.def(),
super::lineitem::Relation::Cake.def().rev(),
super::lineitem::Relation::Order.def(),
super::order::Relation::Customer.def(),
]
}
}

let res: Vec<(baker::Model, Vec<customer::Model>)> = Baker::find()
.find_with_linked(baker::BakedForCustomer)
.order_by_asc(baker::Column::Id)
.all(db)
.await?

Added DeriveValueType derive macro for custom wrapper typesโ€‹

#1720 So now you can use newtypes easily.

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "custom_value_type")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub number: Integer,
// Postgres only
pub str_vec: StringVec,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct Integer(i32);

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct StringVec(pub Vec<String>);

Which saves you the boilerplate of:

impl std::convert::From<StringVec> for Value { .. }

impl TryGetable for StringVec {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I)
-> Result<Self, TryGetError> { .. }
}

impl ValueType for StringVec {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { .. }

fn type_name() -> String { "StringVec".to_owned() }

fn array_type() -> ArrayType { ArrayType::String }

fn column_type() -> ColumnType { ColumnType::String(None) }
}

Enhancements ๐Ÿ†™โ€‹

#1433 Chained AND / OR join ON conditionโ€‹

Added more macro attributes to DeriveRelation

// Entity file

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
// By default, it's `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
)]
TropicalFruit,
// Specify `condition_type = "any"` to override it, now it becomes
// `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
condition_type = "any",
)]
OrTropicalFruit,
}

#1508 Supports entity with composite primary key of arity 12โ€‹

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "primary_key_of_12")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id_1: String,
...
#[sea_orm(primary_key, auto_increment = false)]
pub id_12: bool,
}

#1677 Added UpdateMany::exec_with_returning()โ€‹

let models: Vec<Model> = Entity::update_many()
.col_expr(Column::Values, Expr::expr(..))
.exec_with_returning(db)
.await?;

#1511 Added MigratorTrait::migration_table_name() method to configure the name of migration tableโ€‹

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
// Override the name of migration table
fn migration_table_name() -> sea_orm::DynIden {
Alias::new("override_migration_table_name").into_iden()
}
...
}

#1707 Added DbErr::sql_err() method to parse common database errorsโ€‹

assert!(matches!(
cake.into_active_model().insert(db).await
.expect_err("Insert a row with duplicated primary key")
.sql_err(),
Some(SqlErr::UniqueConstraintViolation(_))
));

assert!(matches!(
fk_cake.insert(db).await
.expect_err("Insert a row with invalid foreign key")
.sql_err(),
Some(SqlErr::ForeignKeyConstraintViolation(_))
));

#1737 Introduced new ConnAcquireErrโ€‹

enum DbErr {
ConnectionAcquire(ConnAcquireErr),
..
}

enum ConnAcquireErr {
Timeout,
ConnectionClosed,
}

#1627 Added DatabaseConnection::ping()โ€‹

|db: DatabaseConnection| {
assert!(db.ping().await.is_ok());
db.clone().close().await;
assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire)));
}

#1708 Added TryInsert that does not panic on empty insertsโ€‹

// now, you can do:
let res = Bakery::insert_many(std::iter::empty())
.on_empty_do_nothing()
.exec(db)
.await;

assert!(matches!(res, Ok(TryInsertResult::Empty)));

#1712 Insert on conflict do nothing to return Okโ€‹

let on = OnConflict::column(Column::Id).do_nothing().to_owned();

// Existing behaviour
let res = Entity::insert_many([..]).on_conflict(on).exec(db).await;
assert!(matches!(res, Err(DbErr::RecordNotInserted)));

// New API; now you can:
let res =
Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await;
assert!(matches!(res, Ok(TryInsertResult::Conflicted)));

#1740, #1755 Replacing sea_query::Iden with sea_orm::DeriveIdenโ€‹

To provide a more consistent interface, sea-query/derive is no longer enabled by sea-orm, as such, Iden no longer works as a derive macro (it's still a trait).

// then:

#[derive(Iden)]
#[iden = "category"]
pub struct CategoryEnum;

#[derive(Iden)]
pub enum Tea {
Table,
#[iden = "AfternoonTea"]
EverydayTea,
}

// now:

#[derive(DeriveIden)]
#[sea_orm(iden = "category")]
pub struct CategoryEnum;

#[derive(DeriveIden)]
pub enum Tea {
Table,
#[sea_orm(iden = "AfternoonTea")]
EverydayTea,
}

New Release Train Ferry ๐Ÿšขโ€‹

It's been the 12th release of SeaORM! Initially, a major version was released every month. It gradually became 2 to 3 months, and now, it's been 6 months since the last major release. As our userbase grew and some are already using SeaORM in production, we understand the importance of having a stable API surface and feature set.

That's why we are committed to:

  1. Reviewing breaking changes with strict scrutiny
  2. Expanding our test suite to cover all features of our library
  3. Never remove features, and consider deprecation carefully

Today, the architecture of SeaORM is pretty solid and stable, and with the 0.12 release where we paid back a lot of technical debt, we will be able to deliver new features and enhancements without breaking. As our major dependency SQLx is not 1.0 yet, technically we cannot be 1.0.

We are still advancing rapidly, and we will always make a new release as soon as SQLx makes a new release, so that you can upgrade everything at once. As a result, the next major release of SeaORM will come out 6 months from now, or when SQLx makes a new release, whichever is earlier.

Community Survey ๐Ÿ“โ€‹

SeaQL is an independent open-source organization. Our goal is to enable developers to build data intensive applications in Rust. If you are using SeaORM, please participate in the SeaQL Community Survey!

By completing this survey, you will help us gather insights into how you, the developer, are using our libraries and identify means to improve your developer experience. We will also publish an annual survey report to summarize our findings.

If you are a happy user of SeaORM, consider writing us a testimonial!

A big thank to DigitalOcean who sponsored our server hosting, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Shane Sveller
ร‰mile Fugulin
Afonso Barracha
Jacob Trueb
Natsuki Ikeguchi
Marlon Mueller-Soppart
KallyDev
Dean Sheather
Manfred Lee
Roland Gorรกcz
IceApinan
Renรฉ Klaฤan
Unnamed Sponsor

What's Next for SeaORM? โ›ตโ€‹

Open-source project is a never-ending work, and we are actively looking for ways to sustain the project. You can support our endeavour by starring & sharing our repositories and becoming a sponsor.

We are considering multiple directions to generate revenue for the organization. If you have any suggestion, or want to join or collaborate with us, please contact us via hello[at]sea-ql.org.

Thank you for your support, and together we can make open-source sustainable.

ยท 5 min read
Chris Tsang

We are pleased to introduce SeaStreamer to the Rust community today. SeaStreamer is a stream processing toolkit to help you build stream processors in Rust.

At SeaQL we want to make Rust the best programming platform for data engineering. Where SeaORM is the essential tool for working with SQL databases, SeaStreamer aims to be your essential toolkit for working with streams.

Currently SeaStreamer provides integration with Kafka and Redis.

Let's have a quick tour of SeaStreamer.

High level async APIโ€‹

  • High level async API that supports both async-std and tokio
  • Mutex-free implementation1: concurrency achieved by message passing
  • A comprehensive type system that guides/restricts you with the API

Below is a basic Kafka consumer:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "kafka://streamer.sea-ql.org:9092/my_stream".parse()?;
let streamer = KafkaStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = KafkaConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_offset_reset(AutoOffsetReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Consumer::stream() returns an object that implements the Stream trait, which allows you to do neat things:

let items = consumer
.stream()
.take(num)
.map(process_message)
.collect::<Vec<_>>()
.await

Trait-based abstract interfaceโ€‹

All SeaStreamer backends implement a common abstract interface, offering you a familiar API. Below is a basic Redis consumer, which is nearly the same as the previous example:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "redis://localhost:6379/my_stream".parse()?;
let streamer = RedisStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = RedisConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_stream_reset(AutoStreamReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Redis Streams Supportโ€‹

SeaStreamer Redis provides a Kafka-like stream semantics:

  • Non-group streaming with AutoStreamReset option
  • Consumer-group-based streaming with auto-ack and/or auto-commit
  • Load balancing among consumers with automatic failover
  • Seek/rewind to point in time

You don't have to call XADD, XREAD, XACK, etc... anymore!

Enum-based generic interfaceโ€‹

The trait-based API requires you to designate the concrete Streamer type for monomorphization, otherwise the code cannot compile.

Akin to how SeaORM implements runtime-polymorphism, SeaStreamer provides a enum-based generic streamer, in which the backend is selected on runtime.

Here is an illustration (full example):

// sea-streamer-socket
pub struct SeaConsumer {
backend: SeaConsumerBackend,
}

enum SeaConsumerBackend {
#[cfg(feature = "backend-kafka")]
Kafka(KafkaConsumer),
#[cfg(feature = "backend-redis")]
Redis(RedisConsumer),
#[cfg(feature = "backend-stdio")]
Stdio(StdioConsumer),
}

// Your code
let uri: StreamerUri = "kafka://localhost:9092".parse()?; // or
let uri: StreamerUri = "redis://localhost:6379".parse()?; // or
let uri: StreamerUri = "stdio://".parse()?;

// SeaStreamer will be backed by Kafka, Redis or Stdio depending on the URI
let streamer = SeaStreamer::connect(uri, Default::default()).await?;

// Set backend-specific options
let mut options = SeaConsumerOptions::new(ConsumerMode::Resumable);
options.set_kafka_consumer_options(|options: &mut KafkaConsumerOptions| { .. });
options.set_redis_consumer_options(|options: &mut RedisConsumerOptions| { .. });
let mut consumer: SeaConsumer = streamer.create_consumer(stream_keys, options).await?;

// You can still retrieve the concrete type
let kafka: Option<&mut KafkaConsumer> = consumer.get_kafka();
let redis: Option<&mut RedisConsumer> = consumer.get_redis();

So you can "write once, stream anywhere"!

Good old unix pipeโ€‹

In SeaStreamer, stdin & stdout can be used as stream source and sink.

Say you are developing some processors to transform a stream in several stages:

./processor_1 --input kafka://localhost:9092/input --output kafka://localhost:9092/stage_1 &
./processor_2 --input kafka://localhost:9092/stage_1 --output kafka://localhost:9092/stage_2 &
./processor_3 --input kafka://localhost:9092/stage_2 --output kafka://localhost:9092/output &

It would be great if we can simply pipe the processors together right?

With SeaStreamer, you can do the following:

./processor_1 --input kafka://localhost:9092/input --output stdio:///stream |
./processor_2 --input stdio:///stream --output stdio:///stream |
./processor_3 --input stdio:///stream --output kafka://localhost:9092/output

All without recompiling the stream processors! Now, you can develop locally with the comfort of using |, >, < and your favourite unix program in the shell.

Testableโ€‹

SeaStreamer encourages you to write tests at all levels:

  • You can execute tests involving several stream processors in the same OS process
  • You can execute tests involving several OS processes by connecting them with pipes
  • You can execute tests involving several stream processors with Redis / Kafka

All against the same piece of code! Let SeaStreamer take away the boilerplate and mocking facility from your codebase.

Below is an example of intra-process testing, which can be run with cargo test without any dependency or side-effects:

let stream = StreamKey::new("test")?;
let mut options = StdioConnectOptions::default();
options.set_loopback(true); // messages produced will be feed back to consumers
let streamer = StdioStreamer::connect(StreamerUri::zero(), options).await?;
let producer = streamer.create_producer(stream.clone(), Default::default()).await?;
let mut consumer = streamer.create_consumer(&[stream.clone()], Default::default()).await?;

for i in 0..5 {
let mess = format!("{}", i);
producer.send(mess)?;
}

let seq = collect(&mut consumer, 5).await;
assert_eq!(seq, [0, 1, 2, 3, 4]);

Getting startedโ€‹

If you are eager to get started with SeaStreamer, you can checkout our set of examples:

  • consumer: A basic consumer
  • producer: A basic producer
  • processor: A basic stream processor
  • resumable: A resumable stream processor that continues from where it left off
  • buffered: An advanced stream processor with internal buffering and batch processing
  • blocking: An advanced stream processor for handling blocking / CPU-bound tasks

Read the official documentation to learn more.

Roadmapโ€‹

A few major components we plan to develop:

  • File Backend
  • Redis Cluster

We welcome you to join our Discussions if you have thoughts or ideas!

Peopleโ€‹

SeaStreamer is designed and developed by the same mind who brought you SeaORM:

Chris Tsang

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.


  1. except sea-streamer-stdio, but only contends on consumer add/dropโ†ฉ

ยท 10 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.11.0!

Data Loaderโ€‹

[#1443, #1238] The LoaderTrait provides an API to load related entities in batches.

Consider this one to many relation:

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

The generated SQL is:

SELECT
"cake"."id" AS "A_id",
"cake"."name" AS "A_name",
"fruit"."id" AS "B_id",
"fruit"."name" AS "B_name",
"fruit"."cake_id" AS "B_cake_id"
FROM "cake"
LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id"
ORDER BY "cake"."id" ASC

The 1 side's (Cake) data will be duplicated. If N is a large number, this would results in more data being transferred over the wire. Using the Loader would ensure each model is transferred only once.

The following loads the same data as above, but with two queries:

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

for (cake, fruits) in cakes.into_iter().zip(fruits.into_iter()) { .. }
SELECT "cake"."id", "cake"."name" FROM "cake"
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit" WHERE "fruit"."cake_id" IN (..)

You can even apply filters on the related entity:

let fruits_in_stock: Vec<Vec<fruit::Model>> = cakes.load_many(
fruit::Entity::find().filter(fruit::Column::Stock.gt(0i32))
db
).await?;
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit"
WHERE "fruit"."stock" > 0 AND "fruit"."cake_id" IN (..)

To learn more, read the relation docs.

Transaction Isolation Level and Access Modeโ€‹

[#1230] The transaction_with_config and begin_with_config allows you to specify the IsolationLevel and AccessMode.

For now, they are only implemented for MySQL and Postgres. In order to align their semantic difference, MySQL will execute SET TRANSACTION commands before begin transaction, while Postgres will execute SET TRANSACTION commands after begin transaction.

db.transaction_with_config::<_, _, DbErr>(
|txn| { ... },
Some(IsolationLevel::ReadCommitted),
Some(AccessMode::ReadOnly),
)
.await?;

let transaction = db
.begin_with_config(IsolationLevel::ReadCommitted, AccessMode::ReadOnly)
.await?;

To learn more, read the transaction docs.

Cast Column Type on Select and Saveโ€‹

[#1304] If you need to select a column as one type but save it into the database as another, you can specify the select_as and the save_as attributes to perform the casting. A typical use case is selecting a column of type citext (case-insensitive text) as String in Rust and saving it into the database as citext. One should define the model field as below:

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ci_table")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(select_as = "text", save_as = "citext")]
pub case_insensitive_text: String
}

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

impl ActiveModelBehavior for ActiveModel {}

Changes to ActiveModelBehaviorโ€‹

[#1328, #1145] The methods of ActiveModelBehavior now have Connection as an additional parameter. It enables you to perform database operations, for example, logging the changes made to the existing model or validating the data before inserting it.

#[async_trait]
impl ActiveModelBehavior for ActiveModel {
/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self {
Self {
uuid: Set(Uuid::new_v4()),
..ActiveModelTrait::default()
}
}

/// Will be triggered before insert / update
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// Logging changes
edit_log::ActiveModel {
action: Set("before_save".into()),
values: Set(serde_json::json!(model)),
..Default::default()
}
.insert(db)
.await?;

Ok(self)
}
}

To learn more, read the entity docs.

Execute Unprepared SQL Statementโ€‹

[#1327] You can execute an unprepared SQL statement with ConnectionTrait::execute_unprepared.

// Use `execute_unprepared` if the SQL statement doesn't have value bindings
db.execute_unprepared(
"CREATE TABLE `cake` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL
)"
)
.await?;

// Construct a `Statement` if the SQL contains value bindings
let stmt = Statement::from_sql_and_values(
manager.get_database_backend(),
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
["Cheese Cake".into()]
);
db.execute(stmt).await?;

Select Into Tupleโ€‹

[#1311] You can select a tuple (or single value) with the into_tuple method.

let res: Vec<(String, i64)> = cake::Entity::find()
.select_only()
.column(cake::Column::Name)
.column(cake::Column::Id.count())
.group_by(cake::Column::Name)
.into_tuple()
.all(&db)
.await?;

Atomic Migrationโ€‹

[#1379] Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite.

You can start a transaction inside each migration to perform operations like seeding sample data for a newly created table.

Types Supportโ€‹

  • [#1325] Support various UUID formats that are available in uuid::fmt module
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "uuid_fmt")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub uuid: Uuid,
pub uuid_braced: uuid::fmt::Braced,
pub uuid_hyphenated: uuid::fmt::Hyphenated,
pub uuid_simple: uuid::fmt::Simple,
pub uuid_urn: uuid::fmt::Urn,
}
  • [#1210] Support vector of enum for Postgres
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub teas: Vec<Tea>,
pub teas_opt: Option<Vec<Tea>>,
}
  • [#1414] Support ActiveEnum field as primary key
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_primary_key")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Tea,
pub category: Option<Category>,
pub color: Option<Color>,
}

Opt-in Unstable Internal APIsโ€‹

By enabling sea-orm-internal feature you opt-in unstable internal APIs including:

Breaking Changesโ€‹

  • [#1366] sea-query has been upgraded to 0.28.x, which comes with some improvements and breaking changes. Please follow the release notes for more details

  • [#1420] sea-orm-cli: generate entity command enable --universal-time flag by default

  • [#1425] Added RecordNotInserted and RecordNotUpdated to DbErr

  • [#1327] Added ConnectionTrait::execute_unprepared method

  • [#1311] The required method of TryGetable changed:

// then
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>;
// now; ColIdx can be `&str` or `usize`
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError>;

So if you implemented it yourself:

impl TryGetable for XXX {
- fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+ fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
- let value: YYY = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
+ let value: YYY = res.try_get_by(idx).map_err(TryGetError::DbErr)?;
..
}
}
  • [#1328] The ActiveModelBehavior trait becomes async trait. If you overridden the default ActiveModelBehavior implementation:
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// ...
}

// ...
}
  • [#1425] DbErr::RecordNotFound("None of the database rows are affected") is moved to a dedicated error variant DbErr::RecordNotUpdated
let res = Update::one(cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.into_active_model()
})
.exec(&db)
.await;

// then
assert_eq!(
res,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);

// now
assert_eq!(res, Err(DbErr::RecordNotUpdated));
  • [#1395] sea_orm::ColumnType was replaced by sea_query::ColumnType
    • Method ColumnType::def was moved to ColumnTypeTrait
    • ColumnType::Binary becomes a tuple variant which takes in additional option sea_query::BlobSize
    • ColumnType::Custom takes a sea_query::DynIden instead of String and thus a new method custom is added (note the lowercase)
// Compact Entity
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "fruit")]
pub struct Model {
- #[sea_orm(column_type = r#"Custom("citext".to_owned())"#)]
+ #[sea_orm(column_type = r#"custom("citext")"#)]
pub column: String,
}
// Expanded Entity
impl ColumnTrait for Column {
type EntityName = Entity;

fn def(&self) -> ColumnDef {
match self {
- Self::Column => ColumnType::Custom("citext".to_owned()).def(),
+ Self::Column => ColumnType::custom("citext").def(),
}
}
}

SeaORM Enhancementsโ€‹

  • [#1256] Refactor schema module to expose functions for database alteration
  • [#1346] Generate compact entity with #[sea_orm(column_type = "JsonBinary")] macro attribute
  • MockDatabase::append_exec_results(), MockDatabase::append_query_results(), MockDatabase::append_exec_errors() and MockDatabase::append_query_errors() [#1367] take any types implemented IntoIterator trait
  • [#1362] find_by_id and delete_by_id take any Into primary key value
  • [#1410] QuerySelect::offset and QuerySelect::limit takes in Into<Option<u64>> where None would reset them
  • [#1236] Added DatabaseConnection::close
  • [#1381] Added is_null getter for ColumnDef
  • [#1177] Added ActiveValue::reset to convert Unchanged into Set
  • [#1415] Added QueryTrait::apply_if to optionally apply a filter
  • Added the sea-orm-internal feature flag to expose some SQLx types
    • [#1297] Added DatabaseConnection::get_*_connection_pool() for accessing the inner SQLx connection pool
    • [#1434] Re-exporting SQLx errors

CLI Enhancementsโ€‹

  • [#846, #1186, #1318] Generate #[serde(skip_deserializing)] for primary key columns
  • [#1171, #1320] Generate #[serde(skip)] for hidden columns
  • [#1124, #1321] Generate entity with extra derives and attributes for model struct

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

Our GitHub Sponsor profile is up! SeaQL.org is an independent open-source organization run by passionate developers. If you enjoy using SeaORM, please star and share our repositories. If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the project.

A big shout out to our sponsors ๐Ÿ˜‡:

Afonso Barracha
ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Nick Price
Roland Gorรกcz
Henrik Giesel
Jacob Trueb
Naoki Ikeguchi
Manfred Lee
Marcus Buffett
efrain2007

What's Next?โ€‹

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

Here is the roadmap for SeaORM 0.12.x.

ยท 2 min read
Chris Tsang

FAQ.02 Why the empty enum Relation {} is needed even if an Entity has no relations?โ€‹

Consider the following example Post Entity:

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

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "posts")]
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 {}

The two lines for defining Relation is quite unnecessary right?

To explain the problem, let's dive slightly deeper into the macro-expanded entity:

The DeriveRelation macro simply implements the RelationTrait:

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => unreachable!()
}
}
}

Which in turn is needed by EntityTrait as an associated type:

impl EntityTrait for Entity {
type Relation = Relation;
...
}

It would be ideal if, when the user does not specify this associated type, the library automatically fills in a stub to satisfy the type system?

Turns out, there is such a feature in Rust! It is an unstable feature called associated_type_defaults.

Basically, it allows trait definitions to specify a default associated type, allowing it to be elided:

// only compiles in nightly
trait EntityTrait {
type Relation: Relation = EmptyRelation;
}

Due to our commitment to stable Rust, this may not land in SeaORM very soon. When it is stabilized, do remind us to implement this feature to get rid of those two lines!

ยท 4 min read
SeaQL Team

SeaQL.org offer internships tailored to university students. In fact, it will be the 3rd cohort in 2023.

The internships normally take place during summer and winter semester breaks. During the internship period, you will work on a project dedicatedly and publish the projectโ€™s outcome at the end.

The striking aspect of our mode of operation is it covers the entire lifecycle of software development, from Design โžก๏ธ Implementation โžก๏ธ Testing โžก๏ธ Delivery. You will be amazed of how much you can achieve in such a short period of time!

To date StarfishQL โœด๏ธ and Seaography ๐Ÿงญ are great projects our team has created in the past year. We pride ourselves on careful planning, consistent execution, and pragmatic approach in software engineering. I spend a huge amount of time on idea evaluation: if the scope of the project is too small, it will be uninteresting, but if it is too large, it will fail to be delivered.

Fellow undergraduates, here are a few good reasons why you should participate in internships at an open-source organization like SeaQL:

  1. A tangible showcase on CV: open-source work is published, inspectable and has real-world impact. We will also ensure that it has good branding, graphics, and visibility.
  2. Not driven by a business process, we do not compromise on quality of work. We do not have a proprietary development process, so itโ€™s all open-source tools with transferable skills.
  3. You will contribute to the community and will interact with people across the world. Collaboration on open source is the best thing humanity ever invented. You will only believe me when you have experienced it first-hand.
  4. Because you are the driver of the project you work on, it allows you to uncover something more about yourself, in particular - abilities and discipline: you always have had under/over-estimated yourself in one aspect or another.

Here are several things you are going to learn:

  1. "Thinking > Programming": the more time you spend on thinking beforehand, the better the code you are going to write. And the more time you spend on reviewing afterwards, the better the code is going to be.
  2. How to organize a codebase. Make good use of the Rust type system to craft a modular, testable codebase.
  3. Test automation. Every SeaQL project is continuously tested, and this is an integral part of our engineering process.
  4. Documentation. Our software aims to provide good documentation that is comprehensive, easy to follow, and fun to read.
  5. Performance tuning. Depending on the project, we might do some benchmarking and optimization. But in general we put you off from writing code that creates unnecessary overhead.

We were a mentor organization in GSoC 2022 and may be in 2023 (update: we were not accepted into GSoC 2023). We also offer internships outside of GSoC. So, what are the requirements when you become a contributor?

  1. Be passionate. You must show your passion in open-source and software engineering, so a good GitHub profile with some participation is needed.
  2. Be dedicated. This is a full-time job. While being fully remote and flexible on hours, you must have no other commitment or duties during the stipulated internship period.
  3. Be open-minded. You should listen carefully to your mentors and act on their advice accordingly.
  4. Write more. Communicate your thoughts and progress on all channels in an organized manner.

Donโ€™t just listen to me though. Here is what our past interns says:

Be well-prepared for your upcoming career in this technology industry! Follow us on GitHub and Twitter now, and stay tuned for future announcements.

ยท 4 min read
Chris Tsang

We are calling for contributors and reviewers for SeaQL projects ๐Ÿ“ข!

The SeaQL userbase has been steadily growing in the past year, and itโ€™s a pleasure for us to have helped individuals and start-ups to build their projects in Rust. However, the volume of questions, issues and pull requests is nearly saturating our core membersโ€™ capacity.

But again, thank you everyone for participating in the community!

If your project depends on SeaQL and you want to help us, here are some suggestions (if you have not already, star all our repositories and follow us on Twitter):

  1. Financial Contribution. You can sponsor us on GitHub and those will be used to cover our expenses. As a courtesy, we listen to our sponsors for their needs and use cases, and we also communicate our organizational development from time-to-time.
  2. Code Contribution. Opening a PR with us is always appreciated! To get started, you can go through our issue trackers and pick one to handle. If you are thinking of developing a substantial feature, start with drafting a "Proposal & Implementation Plan" (PIP).
  3. Knowledge Contribution. There are various formats of knowledge sharing: tutorial, cookbook, QnA and Discord. You can open PRs to our documentation repositories or publish on your own. We will be happy to list it in our learning resources section. Keep an eye on our GitHub Discussions and Discord and help others where you can!
  4. Code Review. This is an important process of our engineering. Right now, only 3 of our core members serve as reviewers. Non-core members can also become reviewers and I invite you to become one!

Now, Iโ€™d like to outline our review policy: for maturing projects, each PR merged has to be approved by at least two reviewers and one of them must be a core member; self-review allowed. Here are some examples:

  • A core member opened a PR, another core member approved โœ…
  • A core member opened a PR, a reviewer approved โœ…
  • A reviewer opened a PR, a core member approved โœ…
  • A reviewer opened a PR, another reviewer approved โ›”
  • A contributor opened a PR, 2 core members approved โœ…
  • A contributor opened a PR, a core member and a reviewer approved โœ…
  • A contributor opened a PR, 2 reviewers approved โ›”

In a nutshell, at least two pairs of trusted eyes should have gone through each PR.

What are the criteria when reviewing a PR?โ€‹

The following questions should all be answered yes.

  1. Implementation, documentation and tests
    1. Is the implementation easy to follow (have meaningful variable and function names)?
    2. Is there sufficient document to the API?
    3. Are there adequate tests covering various cases?
  2. API design
    1. Is the API self-documenting so users can understand its use easily?
    2. Is the API style consistent with our existing API?
    3. Does the API made reasonable use of the type system to enforce constraints?
    4. Are the failure paths and error messages clear?
    5. Are all breaking changes justified and documented?
  3. Functionality
    1. Does the feature make sense in computer science terms?
    2. Does the feature actually work with all our supported backends?
    3. Are all caveats discussed and eliminated / documented?
  4. Architecture
    1. Does it fit with the existing architecture of our codebase?
    2. Is it not going to create technical debt / maintenance burden?
    3. Does it not break abstraction?

1, 2 & 3 are fairly objective and factual, however the answers to 4 probably require some discussion and debate. If a consensus cannot be made, @tyt2y3 will make the final verdict.

Who are the current reviewers?โ€‹

As of today, SeaQL has 3 core members who are also reviewers:

Chris Tsang
Founder. Maintains all projects.
Billy Chan
Founding member. Co-maintainer of SeaORM and Seaography.
Ivan Krivosheev
Joined in 2022. Co-maintainer of SeaQuery.

How to become a reviewer?โ€‹

We are going to invite a few contributors we worked closely with, but you can also volunteer โ€“ the requirement is: you have made substantial code contribution to our projects, and has shown familiarity with our engineering practices.

Over time, when you have made significant contribution to our organization, you can also become a core member.

Letโ€™s build for Rust's future together ๐Ÿฆ€โ€‹

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.28.0! Here are some feature highlights ๐ŸŒŸ:

New IdenStatic trait for static identifierโ€‹

[#508] Representing a identifier with &'static str. The IdenStatic trait looks like this:

pub trait IdenStatic: Iden + Copy + 'static {
fn as_str(&self) -> &'static str;
}

You can derive it easily for your existing Iden. Just changing the #[derive(Iden)] into #[derive(IdenStatic)].

#[derive(IdenStatic)]
enum User {
Table,
Id,
FirstName,
LastName,
#[iden = "_email"]
Email,
}

assert_eq!(User::Email.as_str(), "_email");

New PgExpr and SqliteExpr traits for backend specific expressionsโ€‹

[#519] Postgres specific and SQLite specific expressions are being moved into its corresponding trait. You need to import the trait into scope before construct the expression with those backend specific methods.

// Importing `PgExpr` trait before constructing Postgres expression
use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *};

let query = Query::select()
.columns([Font::Name, Font::Variant, Font::Language])
.from(Font::Table)
.and_where(Expr::val("a").concatenate("b").concat("c").concat("d"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "name", "variant", "language" FROM "font" WHERE 'a' || 'b' || 'c' || 'd'"#
);
// Importing `SqliteExpr` trait before constructing SQLite expression
use sea_query::{extension::sqlite::SqliteExpr, tests_cfg::*, *};

let query = Query::select()
.column(Font::Name)
.from(Font::Table)
.and_where(Expr::col(Font::Name).matches("a"))
.to_owned();

assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"SELECT "name" FROM "font" WHERE "name" MATCH 'a'"#
);

Bug Fixesโ€‹

// given
let (statement, values) = sea_query::Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(Cond::any()
.add(Cond::all()) // empty all() => TRUE
.add(Cond::any()) // empty any() => FALSE
)
.build(sea_query::MysqlQueryBuilder);

// old behavior
assert_eq!(statement, r#"SELECT `id` FROM `glyph`"#);

// new behavior
assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE (TRUE) OR (FALSE)"#
);

// a complex example
let (statement, values) = Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(
Cond::all()
.add(Cond::all().not())
.add(Cond::any().not())
.not(),
)
.build(MysqlQueryBuilder);

assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE NOT ((NOT TRUE) AND (NOT FALSE))"#
);

Breaking Changesโ€‹

  • [#535] MSRV is up to 1.62
# Make sure you're running SeaQuery with Rust 1.62+ ๐Ÿฆ€
$ rustup update
  • [#492] ColumnType::Array definition changed from Array(SeaRc<Box<ColumnType>>) to Array(SeaRc<ColumnType>)
  • [#475] Func::* now returns FunctionCall instead of SimpleExpr
  • [#475] Func::coalesce now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Into<SimpleExpr>
  • [#475] Removed Expr::arg and Expr::args - these functions are no longer needed
  • [#507] Moved all Postgres specific operators to PgBinOper
  • [#476] Expr methods used to accepts Into<Value> now accepts Into<SimpleExpr>
  • [#476] Expr::is_in, Expr::is_not_in now accepts Into<SimpleExpr> instead of Into<Value> and convert it to SimpleExpr::Tuple instead of SimpleExpr::Values
  • [#475] Expr::expr now accepts Into<SimpleExpr> instead of SimpleExpr
  • [#519] Moved Postgres specific Expr methods to new trait PgExpr
  • [#528] Expr::equals now accepts C: IntoColumnRef instead of T: IntoIden, C: IntoIden
use sea_query::{*, tests_cfg::*};

let query = Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.and_where(
Expr::col((Char::Table, Char::FontId))
- .equals(Font::Table, Font::Id)
+ .equals((Font::Table, Font::Id))
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `character`.`font_id` = `font`.`id`"#
);
  • [#525] Removed integer and date time column types' display length / precision option

API Additionsโ€‹

  • [#475] Added SelectStatement::from_function
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.column(ColumnRef::Asterisk)
.from_function(Func::random(), Alias::new("func"))
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT * FROM RAND() AS `func`"#
);
  • [#486] Added binary operators from the Postgres pg_trgm extension
use sea_query::extension::postgres::PgBinOper;

assert_eq!(
Query::select()
.expr(Expr::col(Font::Name).binary(PgBinOper::WordSimilarity, Expr::value("serif")))
.from(Font::Table)
.to_string(PostgresQueryBuilder),
r#"SELECT "name" <% 'serif' FROM "font""#
);
  • [#473] Added ILIKE and NOT ILIKE operators
  • [#510] Added the mul and div methods for SimpleExpr
  • [#513] Added the MATCH, -> and ->> operators for SQLite
use sea_query::extension::sqlite::SqliteBinOper;

assert_eq!(
Query::select()
.column(Char::Character)
.from(Char::Table)
.and_where(Expr::col(Char::Character).binary(SqliteBinOper::Match, Expr::val("test")))
.build(SqliteQueryBuilder),
(
r#"SELECT "character" FROM "character" WHERE "character" MATCH ?"#.to_owned(),
Values(vec!["test".into()])
)
);
  • [#497] Added the FULL OUTER JOIN
  • [#530] Added PgFunc::get_random_uuid
  • [#528] Added SimpleExpr::eq, SimpleExpr::ne, Expr::not_equals
  • [#529] Added PgFunc::starts_with
  • [#535] Added Expr::custom_keyword and SimpleExpr::not
use sea_query::*;

let query = Query::select()
.expr(Expr::custom_keyword(Alias::new("test")))
.to_owned();

assert_eq!(query.to_string(MysqlQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(PostgresQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(SqliteQueryBuilder), r#"SELECT test"#);
  • [#539] Added SimpleExpr::like, SimpleExpr::not_like and Expr::cast_as
  • [#532] Added support for NULLS NOT DISTINCT clause for Postgres
  • [#531] Added Expr::cust_with_expr and Expr::cust_with_exprs
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.expr(Expr::cust_with_expr("data @? ($1::JSONPATH)", "hello"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT data @? ('hello'::JSONPATH)"#
);
  • [#538] Added support for converting &String to Value

Miscellaneous Enhancementsโ€‹

  • [#475] New struct FunctionCall which hold function and arguments
  • [#503] Support BigDecimal, IpNetwork and MacAddress for sea-query-postgres
  • [#511] Made value::with_array module public and therefore making NotU8 trait public
  • [#524] Drop the Sized requirement on implementers of SchemaBuilders

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release Seaography 0.3.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#93] We have upgraded a major dependency:

You might need to upgrade the corresponding dependency in your application as well.

Support Self Referencing Relationโ€‹

[#99] You can now query self referencing models and the inverse of it.

Self referencing relation should be added to the Relation enum, note that the belongs_to attribute must be belongs_to = "Entity".

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

#[derive(
Clone, Debug, PartialEq, DeriveEntityModel,
async_graphql::SimpleObject, seaography::macros::Filter,
)]
#[sea_orm(table_name = "staff")]
#[graphql(complex)]
#[graphql(name = "Staff")]
pub struct Model {
#[sea_orm(primary_key)]
pub staff_id: i32,
pub first_name: String,
pub last_name: String,
pub reports_to_id: Option<i32>,
}

#[derive(
Copy, Clone, Debug, EnumIter, DeriveRelation,
seaography::macros::RelationsCompact
)]
pub enum Relation {
#[sea_orm(
belongs_to = "Entity",
from = "Column::ReportsToId",
to = "Column::StaffId",
)]
SelfRef,
}

impl ActiveModelBehavior for ActiveModel {}

Then, you can query the related models in GraphQL.

{
staff {
nodes {
firstName
reportsToId
selfRefReverse {
staffId
firstName
}
selfRef {
staffId
firstName
}
}
}
}

The resulting JSON

{
"staff": {
"nodes": [
{
"firstName": "Mike",
"reportsToId": null,
"selfRefReverse": [
{
"staffId": 2,
"firstName": "Jon"
}
],
"selfRef": null
},
{
"firstName": "Jon",
"reportsToId": 1,
"selfRefReverse": null,
"selfRef": {
"staffId": 1,
"firstName": "Mike"
}
}
]
}
}

Web Framework Generatorโ€‹

[#74] You can generate seaography project with either Actix or Poem as the web server.

CLI Generator Optionโ€‹

Run seaography-cli to generate seaography code with Actix or Poem as the web framework.

# The command take three arguments, generating project with Poem web framework by default
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# Generating project with Actix web framework
seaography-cli -f actix <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Actixโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
use sea_orm::Database;
use seaography_example_project::*;
// ...

async fn graphql_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(
playground_source(GraphQLPlaygroundConfig::new("http://localhost:8000"))
))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader {
db: database.clone(),
},
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = App::new()
.app_data(Data::new(schema.clone()))
.service(web::resource("/").guard(guard::Post()).to(index))
.service(web::resource("/").guard(guard::Get()).to(graphql_playground));

HttpServer::new(app)
.bind("127.0.0.1:8000")?
.run()
.await
}

Poemโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

[#84] Filtering, sorting and paginating related 1-to-many queries. Note that the pagination is work-in-progress, currently it is in memory pagination.

For example, find all inactive customers, include their address, and their payments with amount greater than 7 ordered by amount the second result. You can execute the query below at our GraphQL playground.

{
customer(
filters: { active: { eq: 0 } }
pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } }
) {
nodes {
customerId
lastName
email
address {
address
}
payment(
filters: { amount: { gt: "7" } }
orderBy: { amount: ASC }
pagination: { pages: { limit: 1, page: 1 } }
) {
nodes {
paymentId
amount
}
pages
current
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
pageInfo {
hasPreviousPage
hasNextPage
endCursor
}
}
}

Integration Examplesโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

Communityโ€‹

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

ยท 7 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.10.0!

Rust 1.65โ€‹

The long-anticipated Rust 1.65 has been released! Generic associated types (GATs) must be the hottest newly-stabilized feature.

How is GAT useful to SeaORM? Let's take a look at the following:

trait StreamTrait<'a>: Send + Sync {
type Stream: Stream<Item = Result<QueryResult, DbErr>> + Send;

fn stream(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a + Send>>;
}

You can see that the Future has a lifetime 'a, but as a side effect the lifetime is tied to StreamTrait.

With GAT, the lifetime can be elided:

trait StreamTrait: Send + Sync {
type Stream<'a>: Stream<Item = Result<QueryResult, DbErr>> + Send
where
Self: 'a;

fn stream<'a>(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream<'a>, DbErr>> + 'a + Send>>;
}

What benefit does it bring in practice? Consider you have a function that accepts a generic ConnectionTrait and calls stream():

async fn processor<'a, C>(conn: &'a C) -> Result<...>
where C: ConnectionTrait + StreamTrait<'a> {...}

The fact that the lifetime of the connection is tied to the stream can create confusion to the compiler, most likely when you are making transactions:

async fn do_transaction<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + TransactionTrait
{
let txn = conn.begin().await?;
processor(&txn).await?;
txn.commit().await?;
}

But now, with the lifetime of the stream elided, it's much easier to work on streams inside transactions because the two lifetimes are now distinct and the stream's lifetime is implicit:

async fn processor<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + StreamTrait {...}

Big thanks to @nappa85 for the contribution.


Below are some feature highlights ๐ŸŒŸ:

Support Array Data Types in Postgresโ€‹

[#1132] Support model field of type Vec<T>. (by @hf29h8sh321, @ikrivosheev, @tyt2y3, @billy1624)

You can define a vector of types that are already supported by SeaORM in the model.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "collection")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub integers: Vec<i32>,
pub integers_opt: Option<Vec<i32>>,
pub floats: Vec<f32>,
pub doubles: Vec<f64>,
pub strings: Vec<String>,
}

Keep in mind that you need to enable the postgres-array feature and this is a Postgres only feature.

sea-orm = { version = "0.10", features = ["postgres-array", ...] }

Better Error Typesโ€‹

[#750, #1002] Error types with parsable database specific error. (by @mohs8421, @tyt2y3)

let mud_cake = cake::ActiveModel {
id: Set(1),
name: Set("Moldy Cake".to_owned()),
price: Set(dec!(10.25)),
gluten_free: Set(false),
serial: Set(Uuid::new_v4()),
bakery_id: Set(None),
};

// Insert a new cake with its primary key (`id` column) set to 1.
let cake = mud_cake.save(db).await.expect("could not insert cake");

// Insert the same row again and it failed
// because primary key of each row should be unique.
let error: DbErr = cake
.into_active_model()
.insert(db)
.await
.expect_err("inserting should fail due to duplicate primary key");

match error {
DbErr::Exec(RuntimeErr::SqlxError(error)) => match error {
Error::Database(e) => {
// We check the error code thrown by the database (MySQL in this case),
// `23000` means `ER_DUP_KEY`: we have a duplicate key in the table.
assert_eq!(e.code().unwrap(), "23000");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}

Run Migration on Any Postgres Schemaโ€‹

[#1056] By default migration will be run on the public schema, you can now override it when running migration on the CLI or programmatically. (by @MattGson, @nahuakang, @billy1624)

For CLI, you can specify the target schema with -s / --database_schema option:

  • via sea-orm-cli: sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema
  • via SeaORM migrator: cargo run -- -u postgres://root:root@localhost/database -s my_schema

You can also run the migration on the target schema programmatically:

let connect_options = ConnectOptions::new("postgres://root:root@localhost/database".into())
.set_schema_search_path("my_schema".into()) // Override the default schema
.to_owned();

let db = Database::connect(connect_options).await?

migration::Migrator::up(&db, None).await?;

Breaking Changesโ€‹

enum ColumnType {
// then
Enum(String, Vec<String>)

// now
Enum {
/// Name of enum
name: DynIden,
/// Variants of enum
variants: Vec<DynIden>,
}
...
}
  • A new method array_type was added to ValueType:
impl sea_orm::sea_query::ValueType for MyType {
fn array_type() -> sea_orm::sea_query::ArrayType {
sea_orm::sea_query::ArrayType::TypeName
}
...
}
  • ActiveEnum::name() changed return type to DynIden:
#[derive(Debug, Iden)]
#[iden = "category"]
pub struct CategoryEnum;

impl ActiveEnum for Category {
// then
fn name() -> String {
"category".to_owned()
}

// now
fn name() -> DynIden {
SeaRc::new(CategoryEnum)
}
...
}

SeaORM Enhancementsโ€‹

CLI Enhancementsโ€‹

Please check here for the complete changelog.

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Henrik Giesel
Jacob Trueb
Marcus Buffett
Unnamed Sponsor
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.11.x.

ยท 2 min read
SeaQL Team

Not long ago we opened a PR "Toggle stacked download graph #5010" resolving Convert download chart from stacked chart to regular chart #3876 for crates.io.

What's it all about?

Problemโ€‹

The download graph on crates.io used to be a stacked graph. With download count of older versions stack on top of newer versions. You might misinterpret the numbers. Consider this, at the first glance, it seems that version 0.9.2 has 1,500+ downloads on Nov 7. But in fact, it has only 237 downloads that day because the graph is showing the cumulative downloads.

crates.io Stacked Download Graph

This makes it hard to compare the download trend of different versions over time. Why this is important? You may ask. It's important to observe the adoption rate of newer version upon release. This paints a general picture if existing users are upgrading to newer version or not.

Solutionโ€‹

The idea is simple but effective: having a dropdown to toggle between stacked and unstacked download graph. With this, one can switch between both display mode, comparing the download trend of different version and observing the most download version in the past 90 days are straightforward and intuitive.

crates.io Unstacked Download Graph

Conclusionโ€‹

This is a great tool for us to gauge the adoption rate of our new releases and we highly encourage user upgrading to newer release that contains feature updates and bug fixes.

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.27.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#356] We have upgraded a major dependency:

  • Upgrade sqlx to 0.6.1

You might need to upgrade the corresponding dependency in your application as well.

Drivers supportโ€‹

We have reworked the way drivers work in SeaQuery: priori to 0.27.0, users have to invoke the sea_query_driver_* macros. Now each driver sqlx, postgres & rusqlite has their own supporting crate, which integrates tightly with the corresponding libraries. Checkout our integration examples below for more details.

[#383] Deprecate sea-query-driver in favour of sea-query-binder

[#422] Rusqlite support is moved to sea-query-rusqlite

[#433] Postgres support is moved to sea-query-postgres

// before
sea_query::sea_query_driver_postgres!();
use sea_query_driver_postgres::{bind_query, bind_query_as};

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build(PostgresQueryBuilder);

let row = bind_query(sqlx::query(&sql), &values)
.fetch_one(&mut pool)
.await
.unwrap();

// now
use sea_query_binder::SqlxBinder;

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build_sqlx(PostgresQueryBuilder);

let row = sqlx::query_with(&sql, values)
.fetch_one(&mut pool)
.await
.unwrap();

// You can now make use of SQLx's `query_as_with` nicely:
let rows = sqlx::query_as_with::<_, StructWithFromRow, _>(&sql, values)
.fetch_all(&mut pool)
.await
.unwrap();

Support sub-query operators: EXISTS, ALL, ANY, SOMEโ€‹

[#118] Added sub-query operators: EXISTS, ALL, ANY, SOME

let query = Query::select()
.column(Char::Id)
.from(Char::Table)
.and_where(
Expr::col(Char::Id)
.eq(
Expr::any(
Query::select().column(Char::Id).from(Char::Table).take()
)
)
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `id` FROM `character` WHERE `id` = ANY(SELECT `id` FROM `character`)"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "id" FROM "character" WHERE "id" = ANY(SELECT "id" FROM "character")"#
);

Support ON CONFLICT WHEREโ€‹

[#366] Added support to ON CONFLICT WHERE

let query = Query::insert()
.into_table(Glyph::Table)
.columns([Glyph::Aspect, Glyph::Image])
.values_panic(vec![
2.into(),
3.into(),
])
.on_conflict(
OnConflict::column(Glyph::Id)
.update_expr((Glyph::Image, Expr::val(1).add(2)))
.target_and_where(Expr::tbl(Glyph::Table, Glyph::Aspect).is_null())
.to_owned()
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `image` = 1 + 2"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);
assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);

Changed cond_where chaining semanticsโ€‹

[#414] Changed cond_where chaining semantics

// Before: will extend current Condition
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.cond_where(Expr::col(Glyph::Id).eq(3))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 OR "id" = 2 OR "id" = 3"#
);
// Before: confusing, since it depends on the order of invocation:
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(3))
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 3 AND ("id" = 1 OR "id" = 2)"#
);
// Now: will always conjoin with `AND`
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(1))
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 AND ("id" = 2 OR "id" = 3)"#
);
// Now: so they are now equivalent
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.cond_where(Expr::col(Glyph::Id).eq(1))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE ("id" = 2 OR "id" = 3) AND "id" = 1"#
);

Added OnConflict::value and OnConflict::valuesโ€‹

[#451] Implementation From<T> for any Into<Value> into SimpleExpr

// Before: notice the tuple
OnConflict::column(Glyph::Id).update_expr((Glyph::Image, Expr::val(1).add(2)))
// After: it accepts `Value` as well as `SimpleExpr`
OnConflict::column(Glyph::Id).value(Glyph::Image, Expr::val(1).add(2))

Improvement to ColumnDef::defaultโ€‹

[#347] ColumnDef::default now accepts Into<SimpleExpr> instead Into<Value>

// Now we can write:
ColumnDef::new(Char::FontId)
.timestamp()
.default(Expr::current_timestamp())

Breaking Changesโ€‹

  • [#386] Changed in_tuples interface to accept IntoValueTuple
  • [#320] Removed deprecated methods
  • [#440] CURRENT_TIMESTAMP changed from being a function to keyword
  • [#375] Update SQLite boolean type from integer to boolean`
  • [#451] Deprecated OnConflict::update_value, OnConflict::update_values, OnConflict::update_expr, OnConflict::update_exprs
  • [#451] Deprecated InsertStatement::exprs, InsertStatement::exprs_panic
  • [#451] Deprecated UpdateStatement::col_expr, UpdateStatement::value_expr, UpdateStatement::exprs
  • [#451] UpdateStatement::value now accept Into<SimpleExpr> instead of Into<Value>
  • [#451] Expr::case, CaseStatement::case and CaseStatement::finally now accepts Into<SimpleExpr> instead of Into<Expr>
  • [#460] InsertStatement::values, UpdateStatement::values now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Value>
  • [#409] Use native api from SQLx for SQLite to work with time
  • [#435] Changed type of ColumnType::Enum from (String, Vec<String>) to Enum { name: DynIden, variants: Vec<DynIden>}

Miscellaneous Enhancementsโ€‹

  • [#336] Added support one dimension Postgres array for SQLx
  • [#373] Support CROSS JOIN
  • [#457] Added support DROP COLUMN for SQLite
  • [#466] Added YEAR, BIT and VARBIT types
  • [#338] Handle Postgres schema name for schema statements
  • [#418] Added %, << and >> binary operators
  • [#329] Added RAND function
  • [#425] Implements Display for Value
  • [#427] Added INTERSECT and EXCEPT to UnionType
  • [#448] OrderedStatement::order_by_customs, OrderedStatement::order_by_columns, OverStatement::partition_by_customs, OverStatement::partition_by_columns now accepts IntoIterator<Item = T> instead of Vec<T>
  • [#452] TableAlterStatement::rename_column, TableAlterStatement::drop_column, ColumnDef::new, ColumnDef::new_with_type now accepts IntoIden instead of Iden
  • [#426] Cleanup IndexBuilder trait methods
  • [#436] Introduce SqlWriter trait
  • [#448] Remove unneeded vec! from examples

Bug Fixesโ€‹

  • [#449] distinct_on properly handles ColumnRef
  • [#461] Removed ON for DROP INDEX for SQLite
  • [#468] Change datetime string format to include microseconds
  • [#452] ALTER TABLE for PosgreSQL with UNIQUE constraint

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 6 min read
SeaQL Team

Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

The design and implementation of Seaography can be found on our release blog post and documentation.

Extending a SeaORM projectโ€‹

Since Seaography is built on top of SeaORM, you can easily build a GraphQL server from a SeaORM project.

Start by adding Seaography and GraphQL dependencies to your Cargo.toml.

Cargo.toml
[dependencies]
sea-orm = { version = "^0.9", features = [ ... ] }
+ seaography = { version = "^0.1", features = [ "with-decimal", "with-chrono" ] }
+ async-graphql = { version = "4.0.10", features = ["decimal", "chrono", "dataloader"] }
+ async-graphql-poem = { version = "4.0.10" }

Then, derive a few macros on the SeaORM entities.

src/entities/film_actor.rs
use sea_orm::entity::prelude::*;

#[derive(
Clone,
Debug,
PartialEq,
DeriveEntityModel,
+ async_graphql::SimpleObject,
+ seaography::macros::Filter,
)]
+ #[graphql(complex)]
+ #[graphql(name = "FilmActor")]
#[sea_orm(table_name = "film_actor")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub actor_id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub film_id: i32,
pub last_update: DateTimeUtc,
}

#[derive(
Copy,
Clone,
Debug,
EnumIter,
DeriveRelation,
+ seaography::macros::RelationsCompact,
)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::film::Entity",
from = "Column::FilmId",
to = "super::film::Column::FilmId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Film,
#[sea_orm(
belongs_to = "super::actor::Entity",
from = "Column::ActorId",
to = "super::actor::Column::ActorId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Actor,
}

We also need to define QueryRoot for the GraphQL server. This define the GraphQL schema.

src/query_root.rs
#[derive(Debug, seaography::macros::QueryRoot)]
#[seaography(entity = "crate::entities::actor")]
#[seaography(entity = "crate::entities::film")]
#[seaography(entity = "crate::entities::film_actor")]
pub struct QueryRoot;
src/lib.rs
use sea_orm::prelude::*;

pub mod entities;
pub mod query_root;

pub use query_root::QueryRoot;

pub struct OrmDataloader {
pub db: DatabaseConnection,
}

Finally, create an executable to drive the GraphQL server.

src/main.rs
use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

Generating a project from databaseโ€‹

If all you have is a database schema, good news! You can setup a GraphQL server without writing a single line of code.

Install seaography-cli, it helps you generate SeaORM entities along with a full Rust project based on a database schema.

cargo install seaography-cli

Run seaography-cli to generate code for the GraphQL server.

# The command take three arguments
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Checkout the example projectsโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

Starting the GraphQL Serverโ€‹

Your GraphQL server is ready to launch! Go to the Rust project root then execute cargo run to spin it up.

$ cargo run

Playground: http://localhost:8000

Visit the GraphQL playground at http://localhost:8000

GraphQL Playground

Query Data via GraphQLโ€‹

Let say we want to get the first 3 films released on or after year 2006 sorted in ascending order of its title.

{
film(
pagination: { limit: 3, page: 0 }
filters: { releaseYear: { gte: "2006" } }
orderBy: { title: ASC }
) {
data {
filmId
title
description
releaseYear
filmActor {
actor {
actorId
firstName
lastName
}
}
}
pages
current
}
}

We got the following JSON result after running the GraphQL query.

{
"data": {
"film": {
"data": [
{
"filmId": 1,
"title": "ACADEMY DINOSAUR",
"description": "An Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": "2006",
"filmActor": [
{
"actor": {
"actorId": 1,
"firstName": "PENELOPE",
"lastName": "GUINESS"
}
},
{
"actor": {
"actorId": 10,
"firstName": "CHRISTIAN",
"lastName": "GABLE"
}
},
// ...
]
},
{
"filmId": 2,
"title": "ACE GOLDFINGER",
"description": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
"releaseYear": "2006",
"filmActor": [
// ...
]
},
// ...
],
"pages": 334,
"current": 0
}
}
}

Behind the scene, the following SQL were queried:

SELECT "film"."film_id",
"film"."title",
"film"."description",
"film"."release_year",
"film"."language_id",
"film"."original_language_id",
"film"."rental_duration",
"film"."rental_rate",
"film"."length",
"film"."replacement_cost",
"film"."rating",
"film"."special_features",
"film"."last_update"
FROM "film"
WHERE "film"."release_year" >= '2006'
ORDER BY "film"."title" ASC
LIMIT 3 OFFSET 0

SELECT "film_actor"."actor_id", "film_actor"."film_id", "film_actor"."last_update"
FROM "film_actor"
WHERE "film_actor"."film_id" IN (1, 3, 2)

SELECT "actor"."actor_id", "actor"."first_name", "actor"."last_name", "actor"."last_update"
FROM "actor"
WHERE "actor"."actor_id" IN (24, 162, 20, 160, 1, 188, 123, 30, 53, 40, 2, 64, 85, 198, 10, 19, 108, 90)

Under the hood, Seaography uses async_graphql::dataloader in querying nested objects to tackle the N+1 problem.

To learn more, checkout the Seaography Documentation.

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL

ยท 4 min read
SeaQL Team

What a fruitful Summer of Code! Today, we are excited to introduce Seaography to the SeaQL community. Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

Motivationโ€‹

We observed that other ecosystems have similar tools such as PostGraphile and Hasura allowing users to query a database via GraphQL with minimal effort upfront. We decided to bring that seamless experience to the Rust ecosystem.

For existing SeaORM users, adding a GraphQL API is straight forward. Start by adding seaography and async-graphql dependencies to your crate. Then, deriving a few extra derive macros to the SeaORM entities. Finally, spin up a GraphQL server to serve queries!

If you are new to SeaORM, no worries, we have your back. You only need to provide a database connection, and seaography-cli will generate the SeaORM entities together with a complete Rust project!

Designโ€‹

We considered two approaches in our initial discussion: 1) blackbox query engine 2) code generator. The drawback with a blackbox query engine is it's difficult to customize or extend its behaviour, making it difficult to develop and operate in the long run. We opted the code generator approach, giving users full control and endless possibilities with the versatile async Rust ecosystem.

This project is separated into the following crates:

  • seaography: The facade crate; exporting macros, structures and helper functions to turn SeaORM entities into GraphQL nodes.

  • seaography-cli: The CLI tool; it generates SeaORM entities along with a full Rust project based on a user-provided database.

  • seaography-discoverer: A helper crate used by the CLI tool to discover the database schema and transform into a generic format.

  • seaography-generator: A helper crate used by the CLI tool to consume the database schema and generate a full Rust project.

  • seaography-derive: A set of procedural macros to derive types and trait implementations on SeaORM entities, turning them into GraphQL nodes.

Featuresโ€‹

  • Relational query (1-to-1, 1-to-N)
  • Pagination on query's root entity
  • Filter with operators (e.g. gt, lt, eq)
  • Order by any column

Getting Startedโ€‹

To quick start, we have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

For more documentation, visit www.sea-ql.org/Seaography.

What's Next?โ€‹

This project passed the first milestone shipping the essential features, but it still has a long way to go. The next milestone would be:

  • Query enhancements
    • Filter related queries
    • Filter based on related queries properties
    • Paginate related queries
    • Order by related queries
  • Cursor based pagination
  • Single entity query
  • Mutations
    • Insert single entity
    • Insert batch entities
    • Update single entity
    • Update batch entities using filter
    • Delete single entity
    • Delete batch entities

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL

ยท 6 min read
SeaQL Team

We are celebrating the milestone of reaching 3,000 GitHub stars across all SeaQL repositories!

This wouldn't have happened without your support and contribution, so we want to thank the community for being with us along the way.

The Journeyโ€‹

SeaQL.org was founded back in 2020. We devoted ourselves into developing open source libraries that help Rust developers to build data intensive applications. In the past two years, we published and maintained four open source libraries: SeaQuery, SeaSchema, SeaORM and StarfishQL. Each library is designed to fill a niche in the Rust ecosystem, and they are made to play well with other Rust libraries.

2020โ€‹

  • Oct 2020: SeaQL founded
  • Dec 2020: SeaQuery first released

2021โ€‹

  • Apr 2021: SeaSchema first released
  • Aug 2021: SeaORM first released
  • Nov 2021: SeaORM reached 0.4.0
  • Dec 2021: SeaQuery reached 0.20.0
  • Dec 2021: SeaSchema reached 0.4.0

2022โ€‹

  • Apr 2022: SeaQL selected as a Google Summer of Code 2022 mentor organization
  • Apr 2022: StarfishQL first released
  • Jul 2022: SeaQuery reached 0.26.2
  • Jul 2022: SeaSchema reached 0.9.3
  • Jul 2022: SeaORM reached 0.9.1
  • Aug 2022: SeaQL reached 3,000+ GitHub stars

Where're We Now?โ€‹

We're pleased by the adoption by the Rust community. We couldn't make it this far without your feedback and contributions.

4 ๐Ÿ“ฆ
Open source projects
5 ๐Ÿฌ
Startups using SeaQL
1,972 ๐ŸŽˆ
Dependent projects
131 ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ
Contributors
1,061 โœ…
Merged PRs & resolved issues
3,158 โญ
GitHub stars
432 ๐Ÿ—ฃ๏ธ
Discord members
87,937 โŒจ๏ธ
Lines of Rust
667,769 ๐Ÿ’ฟ
Downloads on crates.io

* as of Aug 12

Core Membersโ€‹

Our team has grown from two people initially into four. We always welcome passionate engineers to join us!

Chris Tsang
Founder. Led the initial development and maintaining the projects.
Billy Chan
Founding member. Contributed many features and bug fixes. Keeps the community alive.
Ivan Krivosheev
Joined in 2022. Contributed many features and bug fixes, most notably to SeaQuery.
Sanford Pun
Developed StarfishQL and wrote SeaORM's tutorial.

Special Thanksโ€‹

Marco Napetti
Contributed transaction, streaming and tracing API to SeaORM.
nitnelave
Contributed binder crate and other improvements to SeaQuery.
Sam Samai
Developed SeaORM's test suite and demo schema.
Daniel Lyne
Developed SeaSchema's Postgres implementation.
Charles Chege
Developed SeaSchema's SQLite implementation.

Sponsorsโ€‹

If you are feeling generous, a small donation will be greatly appreciated.

A big shout out to our sponsors ๐Ÿ˜‡:

ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
Unnamed Sponsor

Contributorsโ€‹

Many features and enhancements are actually proposed and implemented by the community. We want to take this chance to thank all our contributors!

What's Next?โ€‹

We have two ongoing Summer of Code 2022 projects to enrich the SeaQL ecosystem, planning to be released later this year. In the meantime, we're focusing on improving existing SeaQL libraries until reaching version 1.0, we'd love to hear comments and feedback from the community.

If you like what we do, consider starring, commenting, sharing, contributing and together building for Rust's future!

ยท 3 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.26.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#356] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

VALUES listsโ€‹

[#351] Add support for VALUES lists

// SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x"
let query = SelectStatement::new()
.expr(Expr::asterisk())
.from_values(vec![(1i32, "hello"), (2, "world")], Alias::new("x"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x""#
);

Introduce sea-query-binderโ€‹

[#273] Native support SQLx without marcos

use sea_query_binder::SqlxBinder;

// Create SeaQuery query with prepare SQLx
let (sql, values) = Query::select()
.columns([
Character::Id,
Character::Uuid,
Character::Character,
Character::FontSize,
Character::Meta,
Character::Decimal,
Character::BigDecimal,
Character::Created,
Character::Inet,
Character::MacAddress,
])
.from(Character::Table)
.order_by(Character::Id, Order::Desc)
.build_sqlx(PostgresQueryBuilder);

// Execute query
let rows = sqlx::query_as_with::<_, CharacterStructChrono, _>(&sql, values)
.fetch_all(&mut pool)
.await?;

// Print rows
for row in rows.iter() {
println!("{:?}", row);
}

CASE WHEN statement supportโ€‹

[#304] Add support for CASE WHEN statement

let query = Query::select()
.expr_as(
CaseStatement::new()
.case(Expr::tbl(Glyph::Table, Glyph::Aspect).is_in(vec![2, 4]), Expr::val(true))
.finally(Expr::val(false)),
Alias::new("is_even")
)
.from(Glyph::Table)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT (CASE WHEN ("glyph"."aspect" IN (2, 4)) THEN TRUE ELSE FALSE END) AS "is_even" FROM "glyph""#
);

Add support for Ip(4,6)Network and MacAddressโ€‹

[#309] Add support for Network types in PostgreSQL backend

Introduce sea-query-attrโ€‹

[#296] Proc-macro for deriving Iden enum from struct

use sea_query::gen_type_def;

#[gen_type_def]
pub struct Hello {
pub name: String
}

println!("{:?}", HelloTypeDef::Name);

Add ability to alter foreign keysโ€‹

[#299] Add support for ALTER foreign Keys

let foreign_key_char = TableForeignKey::new()
.name("FK_character_glyph")
.from_tbl(Char::Table)
.from_col(Char::FontId)
.from_col(Char::Id)
.to_tbl(Glyph::Table)
.to_col(Char::FontId)
.to_col(Char::Id)
.to_owned();

let table = Table::alter()
.table(Character::Table)
.add_foreign_key(&foreign_key_char)
.to_owned();

assert_eq!(
table.to_string(PostgresQueryBuilder),
vec![
r#"ALTER TABLE "character""#,
r#"ADD CONSTRAINT "FK_character_glyph""#,
r#"FOREIGN KEY ("font_id", "id") REFERENCES "glyph" ("font_id", "id")"#,
r#"ON DELETE CASCADE ON UPDATE CASCADE,"#,
]
.join(" ")
);

Select DISTINCT ONโ€‹

[#250]

let query = Query::select()
.from(Char::Table)
.distinct_on(vec![Char::Character])
.column(Char::Character)
.column(Char::SizeW)
.column(Char::SizeH)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT DISTINCT ON ("character") "character", "size_w", "size_h" FROM "character""#
);

Miscellaneous Enhancementsโ€‹

  • [#353] Support LIKE ... ESCAPE ... expression
  • [#306] Move escape and unescape string to backend
  • [#365] Add method to make a column nullable
  • [#348] Add is & is_not to Expr
  • [#349] Add CURRENT_TIMESTAMP function
  • [#345] Add in_tuple method to Expr
  • [#266] Insert Default
  • [#324] Make sea-query-driver an optional dependency
  • [#334] Add ABS function
  • [#332] Support IF NOT EXISTS when create index
  • [#314] Support different blob types in MySQL
  • [#331] Add VarBinary column type
  • [#335] RETURNING expression supporting SimpleExpr

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 5 min read
Chris Tsang

It's hard to pin down the exact date, but I think SeaQL.org was setup in July 2020, a little over a year ago. Over the course of the year, SeaORM went from 0.1 to 0.9 and the number of users kept growing. I would like to outline our engineering process in this blog post, and perhaps it can serve as a reference or guidance to prospective contributors and the future maintainer of this project.

In the open source world, the Benevolent Dictator for Life (BDL) model underpins a number of successful open source projects. That's not me! As a maintainer, I believe in an open, bottom-up, iterative and progressive approach. Let me explain each of these words and what they mean to me.

Openโ€‹

Open as in source availability, but also engineering. We always welcome new contributors! We'd openly discuss ideas and designs. I would often explain why a decision was made in the first place for various things. The project is structured not as a monorepo, but several interdependent repos. This reduces the friction for new contributors, because they can have a smaller field of vision to focus on solving one particular problem at hand.

Bottom-upโ€‹

We rely on users to file feature requests, bug reports and of course pull requests to drive the project forward. The great thing is, for every feature / bug fix, there is a use case for it and a confirmation from a real user that it works and is reasonable. As a maintainer, I could not have first hand experience for all features and so could not understand some of the pain points.

Iterativeโ€‹

Open source software is imperfect, impermanent and incomplete. While I do have a grand vision in mind, we do not try rushing it all the way in one charge, nor keeping a project secret until it is 'complete'. Good old 'release early, release often' - we would release an initial working version of a tool, gather user feedback and improve upon it, often reimplementing a few things and break a few others - which brings us to the next point.

Progressiveโ€‹

Favour progression. Always look forward and leave legacy behind. It does not mean that we would arbitrary break things, but when a decision is made, we'd always imagine how the software should be without historic context. We'd provide migrate paths and encourage users to move forward with us. After all, Rust is a young and evolving language! You may or may not know that async was just stabilized in 2020.

Enough said for the philosophy, let's now talk about the actual engineering process.

1. Idea & Designโ€‹

We first have some vague idea on what problem we want to tackle. As we put in more details to the use case, we can define the problem and brainstorm solutions. Then we look for workable ways to implement that in Rust.

2. Implementationโ€‹

An initial proof of concept is appreciated. We iterate on the implementation to reduce the impact and improve the maintainability.

3. Testingโ€‹

We rely on automated tests. Every feature should come with corresponding tests, and a release is good if and only if all tests are green. Which means for features not covered by our test suite, it is an uncertainty to when we would break them. So if certain undocumented feature is important to you, we encourage you to add that to our test suite.

4. Documentationโ€‹

Coding is not complete without documentation. Rust doc tests kill two birds with one stone and so is greatly appreciated. For SeaORM we have separate documentation repository and tutorial repository. It takes a lot of effort to maintain those to be up to date, and right now it's mostly done by our core contributors.

5. Releaseโ€‹

We run on a release train model, although the frequency varies. The ethos is to have small number breaking changes often. At one point, SeaQuery has a new release every week. SeaORM runs on monthly, although it more or less relaxes to bimonthly now. At any time, we maintain two branches, the latest release and master. PRs are always merged into master, and if it is non-breaking (and worthy) I would backport it to the release branch and make a minor release. At the end, I want to maintain momentum and move forward together with the community. Users can have a rough expectation on when merges will be released. And there are just lots of change we cannot avoid a breaking release as of the current state of the Rust ecosystem. Users are advised to upgrade regularly, and we ship along many small improvements to encourage that.

Conclusionโ€‹

Open source software is a collaborative effort and thank you all who participated! Also a big thanks to SeaQL's core contributors who made wonders. If you have not already, I invite you to star all our repositories. If you want to support us materially, a small donation would make a big difference. SeaQL the organization is still in its infancy, and your support is vital to SeaQL's longevity and the prospect of the Rust community.

ยท 11 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.9.0 today! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#834] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

Proposed by:
Rob Gilson
boraarslan
Contributed by:
Billy Chan

Cursor Paginationโ€‹

[#822] Paginate models based on column(s) such as the primary key.

// Create a cursor that order by `cake`.`id`
let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id);

// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100
cursor.after(1).before(100);

// Get first 10 rows (order by `cake`.`id` ASC)
let rows: Vec<cake::Model> = cursor.first(10).all(db).await?;

// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order)
let rows: Vec<cake::Model> = cursor.last(10).all(db).await?;
Proposed by:
Lucas Berezy
Contributed by:
ร‰mile Fugulin
Billy Chan

Insert On Conflictโ€‹

[#791] Insert an active model with on conflict behaviour.

let orange = cake::ActiveModel {
id: ActiveValue::set(2),
name: ActiveValue::set("Orange".to_owned()),
};

// On conflict do nothing:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING
cake::Entity::insert(orange.clone())
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.do_nothing()
.to_owned()
)
.exec(db)
.await?;

// On conflict do update:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name"
cake::Entity::insert(orange)
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.update_column(cake::Column::Name)
.to_owned()
)
.exec(db)
.await?;
Proposed by:
baoyachi. Aka Rust Hairy crabs
Contributed by:
liberwang1013

Join Table with Custom Conditions and Table Aliasโ€‹

[#793, #852] Click Custom Join Conditions and Custom Joins to learn more.

assert_eq!(
cake::Entity::find()
.column_as(
Expr::tbl(Alias::new("fruit_alias"), fruit::Column::Name).into_simple_expr(),
"fruit_name"
)
.join_as(
JoinType::LeftJoin,
cake::Relation::Fruit
.def()
.on_condition(|_left, right| {
Expr::tbl(right, fruit::Column::Name)
.like("%tropical%")
.into_condition()
}),
Alias::new("fruit_alias")
)
.build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake`.`id`, `cake`.`name`, `fruit_alias`.`name` AS `fruit_name` FROM `cake`",
"LEFT JOIN `fruit` AS `fruit_alias` ON `cake`.`id` = `fruit_alias`.`cake_id` AND `fruit_alias`.`name` LIKE '%tropical%'",
]
.join(" ")
);
Proposed by:
Chris Tsang
Tuetuopay
Loรฏc
Contributed by:
Billy Chan
Matt
liberwang1013

(de)serialize Custom JSON Typeโ€‹

[#794] JSON stored in the database could be deserialized into custom struct in Rust.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
// JSON column defined in `serde_json::Value`
pub json: Json,
// JSON column defined in custom struct
pub json_value: KeyValue,
pub json_value_opt: Option<KeyValue>,
}

// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize`
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct KeyValue {
pub id: i32,
pub name: String,
pub price: f32,
pub notes: Option<String>,
}
Proposed by:
Mara Schulke
Chris Tsang
Contributed by:
Billy Chan

Derived Migration Nameโ€‹

[#736] Introduce DeriveMigrationName procedural macros to infer migration name from the file name.

use sea_orm_migration::prelude::*;

// Used to be...
pub struct Migration;

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

// Now... derive `DeriveMigrationName`,
// no longer have to specify the migration name explicitly
#[derive(DeriveMigrationName)]
pub struct Migration;

#[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
}
}
Proposed by:
Chris Tsang
Contributed by:
smonv
Lukas Potthast
Billy Chan

SeaORM CLI Improvementsโ€‹

  • [#735] Improve logging of generate entity command
  • [#588] Generate enum with numeric like variants
  • [#755] Allow old pending migration to be applied
  • [#837] Skip generating entity for ignored tables
  • [#724] Generate code for time crate
  • [#850] Add various blob column types
  • [#422] Generate entity files with Postgres's schema name
  • [#851] Skip checking connection string for credentials
Proposed & Contributed by:
ttys3
kyoto7250
yb3616
ร‰mile Fugulin
Bastian
Nahua
Mike
Frank Horvath
Maikel Wever

Miscellaneous Enhancementsโ€‹

  • [#800] Added sqlx_logging_level to ConnectOptions
  • [#768] Added num_items_and_pages to Paginator
  • [#849] Added TryFromU64 for time
  • [#853] Include column name in TryGetError::Null
  • [#778] Refactor stream metrics
Proposed & Contributed by:
SandaruKasa
Eric
ร‰mile Fugulin
Renato Dinhani
kyoto7250
Marco Napetti

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
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.10.x.

ยท 4 min read
SeaQL Team

We are thrilled to announce that we will bring in four contributors this summer! Two of them are sponsored by Google while two of them are sponsored by SeaQL.

A GraphQL Framework on Top of SeaORMโ€‹

Panagiotis Karatakis

I'm Panagiotis, I live in Athens Greece and currently I pursue my second bachelors on economic sciences. My first bachelors was on computer science and I've a great passion on studying and implementing enterprise software solutions. I know Rust the last year and I used it almost daily for a small startup project that me and my friends build for a startup competition.

I'll be working on creating a CLI tool that will explore a database schema and then generate a ready to build async-graphql API. The tool will allow quick integration with the SeaQL and Rust ecosystems as well as GraphQL. To be more specific, for database exploring I'll use sea-schema and sea-orm-codegen for entity generation, my job is to glue those together with async-graphql library. You can read more here.

SQL Interpreter for Mock Testingโ€‹

Samyak Sarnayak

I'm Samyak Sarnayak, a final year Computer Science student from Bangalore, India. I started learning Rust around 6-7 months ago and it feels like I have found the perfect language for me :D. It does not have a runtime, has a great type system, really good compiler errors, good tooling, some functional programming patterns and metaprogramming. You can find more about me on my GitHub profile.

I'll be working on a new SQL interpreter for mock testing. This will be built specifically for testing and so the emphasis will be on correctness - it can be slow but the operations must always be correct. I'm hoping to build a working version of this and integrate it into the existing tests of SeaORM. Here is the discussion for this project.

Support TiDB in the SeaQL Ecosystemโ€‹

Edit: This project was canceled.

Query Linter for SeaORMโ€‹

Edit: This project was canceled.

Mentorsโ€‹

Chris Tsang

I am a strong believer in open source. I started my GitHub journey 10 years ago, when I published my first programming library. I had been looking for a programming language with speed, ergonomic and expressiveness. Until I found Rust.

Seeing a niche and demand for data engineering tools in the Rust ecosystem, I founded SeaQL in 2020 and have been leading the development and maintaining the libraries since then.


Billy Chan

Hey, this is Billy from Hong Kong. I've been using open-source libraries ever since I started coding but it's until 2020, I dedicated myself to be a Rust open-source developer.

I was also a full-stack developer specialized in formulating requirement specifications for user interfaces and database structures, implementing and testing both frontend and backend from ground up, finally releasing the MVP for production and maintaining it for years to come.

I enjoy working with Rustaceans across the globe, building a better and sustainable ecosystem for Rust community. If you like what we do, consider starring, commenting, sharing and contributing, it would be much appreciated.


Sanford Pun

I'm Sanford, an enthusiastic software engineer who enjoys problem-solving! I've worked on Rust for a couple of years now. During my early days with Rust, I focused more on the field of graphics/image processing, where I fell in love with what the language has to offer! This year, I've been exploring data engineering in the StarfishQL project.

A toast to the endless potential of Rust!

Communityโ€‹

If you are interested in the projects and want to share your thoughts, please star and watch the SeaQL/summer-of-code repository on GitHub and join us on our Discord server!

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.8.0 today! Here are some feature highlights ๐ŸŒŸ:

Migration Utilities Moved to sea-orm-migration crateโ€‹

[#666] Utilities of SeaORM migration have been moved from sea-schema to sea-orm-migration crate. Users are advised to upgrade from older versions with the following steps:

  1. Bump sea-orm version to 0.8.0.

  2. Replace sea-schema dependency with sea-orm-migration in your migration crate.

    migration/Cargo.toml
    [dependencies]
    - sea-schema = { version = "^0.7.0", ... }
    + sea-orm-migration = { version = "^0.8.0" }
  3. Find and replace use sea_schema::migration:: with use sea_orm_migration:: in your migration crate.

    - use sea_schema::migration::prelude::*;
    + use sea_orm_migration::prelude::*;

    - use sea_schema::migration::*;
    + use sea_orm_migration::*;
Designed by:

Chris Tsang
Contributed by:

Billy Chan

Generating New Migrationโ€‹

[#656] You can create a new migration with the migrate generate subcommand. This simplifies the migration process, as new migrations no longer need to be added manually.

# A migration file `MIGRATION_DIR/src/mYYYYMMDD_HHMMSS_create_product_table.rs` will be created.
# And, the migration file will be imported and included in the migrator located at `MIGRATION_DIR/src/lib.rs`.
sea-orm-cli migrate generate create_product_table
Proposed & Contributed by:

Viktor Bahr

Inserting One with Defaultโ€‹

[#589] Insert a row populate with default values. Note that the target table should have default values defined for all of its columns.

let pear = fruit::ActiveModel {
..Default::default() // all attributes are `NotSet`
};

// The SQL statement:
// - MySQL: INSERT INTO `fruit` VALUES ()
// - SQLite: INSERT INTO "fruit" DEFAULT VALUES
// - PostgreSQL: INSERT INTO "fruit" VALUES (DEFAULT) RETURNING "id", "name", "cake_id"
let pear: fruit::Model = pear.insert(db).await?;
Proposed by:

Crypto-Virus
Contributed by:

Billy Chan

Checking if an ActiveModel is changedโ€‹

[#683] You can check whether any field in an ActiveModel is Set with the help of the is_changed method.

let mut fruit: fruit::ActiveModel = Default::default();
assert!(!fruit.is_changed());

fruit.set(fruit::Column::Name, "apple".into());
assert!(fruit.is_changed());
Proposed by:

Karol Fuksiewicz
Contributed by:

Kirawi

Minor Improvementsโ€‹

  • [#670] Add max_connections option to sea-orm-cli generate entity subcommand
  • [#677] Derive Eq and Clone for DbErr
Proposed & Contributed by:

benluelo

Sebastien Guillemot

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. It can be integrated easily with common RESTful frameworks and also gRPC frameworks; check out our new Tonic example to see how it works. More examples wanted!

Who's using SeaORM?โ€‹

The following products are powered by SeaORM:



A lightweight web security auditing toolkit

The enterprise ready webhooks service

A personal search engine

SeaORM is the foundation of StarfishQL, an experimental graph database and query engine.

For more projects, see Built with SeaORM.

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.9.x.

GSoC 2022โ€‹

We are super excited to be selected as a Google Summer of Code 2022 mentor organization. The application is now closed, but the program is about to start! If you have thoughts over how we are going to implement the project ideas, feel free to participate in the discussion.

ยท 2 min read
Chris Tsang

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.

ยท 8 min read
SeaQL Team

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 thatserde 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.ย a,ย a-,ย a0,ย  ...,ย zyx_test,ย zz-buffer,ย z_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.ย a,ย a-,ย a-bot,ย  ...,ย zzp-tools,ย zzz,ย z_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.

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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!

ยท 2 min read
SeaQL Team

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

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 2 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 5 min read
Chris Tsang

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

ยท One min read
Chris Tsang

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.

ยท One min read
Chris Tsang

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.

ยท One min read
Chris Tsang

One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren't many choices. So we have to write our own.

December last year, we released SeaQuery, and received welcoming responses from the community. We decided to push the project further and develop a full blown async ORM.

It has been a bumpy ride, as designing an async ORM requires working within and sometimes around Rust's unique type system. After several iterations of experimentation, I think we've attained a balance between static & dynamic and compile-time & run-time that it offers benefits of the Rust language while still be familiar and easy-to-work-with for those who come from other languages.

SeaORM is tentative to be released in Sep 2021 and stabilize in May 2022. We hope that SeaORM will become a go-to choice for working with databases in Rust and that the Rust language will be adopted by more organizations in building applications.

If you are intrigued like I do, please stay in touch and join the community.

Share your thoughts here.

- + \ No newline at end of file diff --git a/blog/rss.xml b/blog/rss.xml index c8639d069ba..ee32c7887e1 100644 --- a/blog/rss.xml +++ b/blog/rss.xml @@ -14,7 +14,7 @@ https://www.sea-ql.org/blog/2024-01-23-whats-new-in-seaorm-0.12.x Tue, 23 Jan 2024 00:00:00 GMT -

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. +

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL]]>
news diff --git a/blog/search/index.html b/blog/search/index.html index 5f32c533445..4a0c3389dd1 100644 --- a/blog/search/index.html +++ b/blog/search/index.html @@ -10,13 +10,13 @@ - +

Search the documentation

- + \ No newline at end of file diff --git a/blog/tags/index.html b/blog/tags/index.html index c0aac2c3c8c..1c165fac0ab 100644 --- a/blog/tags/index.html +++ b/blog/tags/index.html @@ -10,13 +10,13 @@ - + - + \ No newline at end of file diff --git a/blog/tags/news/index.html b/blog/tags/news/index.html index 5886e3dc11c..33bd76c9db4 100644 --- a/blog/tags/news/index.html +++ b/blog/tags/news/index.html @@ -10,12 +10,12 @@ - +
-

37 posts tagged with "news"

View All Tags

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. +

37 posts tagged with "news"

View All Tags

ยท 7 min read
SeaQL Team
SeaORM 0.12 Banner

It had been a while since the initial SeaORM 0.12 release. This blog post summarizes the new features and enhancements introduced in SeaORM 0.12.2 through 0.12.12!

Celebrating 2M downloads on crates.io ๐Ÿ“ฆโ€‹

We've just reached the milestone of 2,000,000 all time downloads on crates.io. It's a testament to SeaORM's adoption in professional use. Thank you to all our users for your trust and for being a part of our community.

New Featuresโ€‹

Entity format updateโ€‹

  • #1898 Add support for root JSON arrays (requires the json-array / postgres-array feature)! It involved an intricate type system refactor to work around the orphan rule.
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(column_type = "Json")]
pub struct_vec: Vec<JsonColumn>,
}

#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct JsonColumn {
pub value: String,
}
  • #2009 Added comment attribute for Entity; create_table_from_entity now supports comment on MySQL
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "applog", comment = "app logs")]
pub struct Model {
#[sea_orm(primary_key, comment = "ID")]
pub id: i32,
#[sea_orm(comment = "action")]
pub action: String,
pub json: Json,
pub created_at: DateTimeWithTimeZone,
}

Cursor paginator improvementsโ€‹

  • #2037 Added descending order to Cursor:
// (default behaviour) Before 5 ASC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.before(5);

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 1 },
Model { id: 2 },
Model { id: 3 },
Model { id: 4 },
]
);

// (new API) After 5 DESC, i.e. id < 5

let mut cursor = Entity::find().cursor_by(Column::Id);
cursor.after(5).desc();

assert_eq!(
cursor.first(4).all(db).await?,
[
Model { id: 4 },
Model { id: 3 },
Model { id: 2 },
Model { id: 1 },
]
);
  • #1826 Added cursor support to SelectTwo:
// Join with linked relation; cursor by first table's id

cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.cursor_by(cake::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

// Join with relation; cursor by the 2nd table's id

cake::Entity::find()
.find_also_related(Fruit)
.cursor_by_other(fruit::Column::Id)
.before(10)
.first(2)
.all(&db)
.await?

Added "proxy" to database backendโ€‹

#1881, #2000 Added "proxy" to database backend (requires feature flag proxy).

It enables the possibility of using SeaORM on edge / client-side! See the GlueSQL demo for an example.

Enhancementsโ€‹

  • #1954 [sea-orm-macro] Added #[sea_orm(skip)] to FromQueryResult derive macro
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize, FromQueryResult)]
pub struct PublicUser {
pub id: i64,
pub name: String,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[sea_orm(skip)]
pub something: Something,
}
  • #1598 [sea-orm-macro] Added support for Postgres arrays in FromQueryResult impl of JsonValue
// existing API:

assert_eq!(
Entity::find_by_id(1).one(db).await?,
Some(Model {
id: 1,
name: "Collection 1".into(),
integers: vec![1, 2, 3],
teas: vec![Tea::BreakfastTea],
colors: vec![Color::Black],
})
);

// new API:

assert_eq!(
Entity::find_by_id(1).into_json().one(db).await?,
Some(json!({
"id": 1,
"name": "Collection 1",
"integers": [1, 2, 3],
"teas": ["BreakfastTea"],
"colors": [0],
}))
);
  • #1828 [sea-orm-migration] Check if an index exists
use sea_orm_migration::prelude::*;
#[derive(DeriveMigrationName)]
pub struct Migration;
#[async_trait::async_trait]
impl MigrationTrait for Migration {
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...

// Make sure the index haven't been created
assert!(!manager.has_index("cake", "cake_name_index").await?);

manager
.create_index(
Index::create()
.name("cake_name_index")
.table(Cake::Table)
.col(Cake::Name)
.to_owned(),
)
.await?;

Ok(())
}

async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
// ...
}
}
  • #2030 Improve query performance of Paginator's COUNT query
  • #2055 Added SQLx slow statements logging to ConnectOptions
  • #1867 Added QuerySelect::lock_with_behavior
  • #2002 Cast enums in is_in and is_not_in
  • #1999 Add source annotations to errors
  • #1960 Implement StatementBuilder for sea_query::WithQuery
  • #1979 Added method expr_as_ that accepts self
  • #1868 Loader: use ValueTuple as hash key
  • #1934 [sea-orm-cli] Added --enum-extra-derives
  • #1952 [sea-orm-cli] Added --enum-extra-attributes
  • #1693 [sea-orm-cli] Support generation of related entity with composite foreign key

Bug fixesโ€‹

  • #1855, #2054 [sea-orm-macro] Qualify types in DeriveValueType macro
  • #1953 [sea-orm-cli] Fix duplicated active enum use statements on generated entities
  • #1821 [sea-orm-cli] Fix entity generation for non-alphanumeric enum variants
  • #2071 [sea-orm-cli] Fix entity generation for relations with composite keys
  • #1800 Fixed find_with_related consolidation logic
  • 5a6acd67 Fixed Loader panic on empty inputs

Upgradesโ€‹

  • #1984 Upgraded axum example to 0.7
  • #1858 Upgraded chrono to 0.4.30
  • #1959 Upgraded rocket to 0.5.0
  • Upgraded sea-query to 0.30.5
  • Upgraded sea-schema to 0.14.2
  • Upgraded salvo to 0.50

House Keepingโ€‹

  • #2057 Fix clippy warnings on 1.75
  • #1811 Added test cases for find_xxx_related/linked

Release planningโ€‹

In the announcement blog post of SeaORM 0.12, we stated we want to reduce the frequency of breaking releases while maintaining the pace for feature updates and enhancements. I am glad to say we've accomplished that!

There are still a few breaking changes planned for the next major release. After some discussions and consideration, we decided that the next major release will be a release candidate for 1.0!

A big thank to DigitalOcean who sponsored our servers, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Gold Sponsorsโ€‹

Sponsorsโ€‹

ร‰mile Fugulin
Afonso Barracha
Shane Sveller
Dean Sheather
Marcus Buffett
Renรฉ Klaฤan
IceApinan
Jacob Trueb
Kentaro Tanaka
Natsuki Ikeguchi
Marlon Mueller-Soppart
ul
Manfred Lee
KallyDev
Daniel Gallups
Coolpany-SE

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. Our stickers are made with a premium water-resistant vinyl with a unique matte finish. Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL

ยท 10 min read
SeaQL Team

524 members of the SeaQL community from 41 countries kindly contributed their thoughts on using SeaQL libraries, learning Rust and employing Rust in their day to day development lives. From these responses we hope to get an understanding of where the SeaQL and Rust community stands in 2023.

This is our first community survey, we will conduct the survey annually to keep track of how the community evolves over time.

Demographicsโ€‹

Q. Where are you located in?โ€‹

Participants are from 41 countries across the world!

Other: ArgentinaAustraliaAustriaBelarusBelgiumCyprusCzechiaDenmarkHungaryIranIrelandItalyJapanKazakstanKoreaMongoliaNigeriaNorwayPeruPolandSlovakiaSouth AfricaSpainSwedenTaiwan ThailandTurkeyUkraine

Use of SeaQL Librariesโ€‹

Q. Are you using SeaQL libraries in building a project?โ€‹

Q. Which SeaQL libraries are you using in building a project?โ€‹

Other: SeaographySeaStreamer

Q. Are you using SeaQL libraries in a personal, academic or professional context?โ€‹

Q. Why did you choose SeaQL libraries?โ€‹

Other: Async support, future proof and good documentationGood Query PerformanceIt was recommended on websites and YouTubeDoes not use SQL for migrationsBeginner-friendly and easy to get startedEasy to translate from Eloquent ORM knowledgeCan drop in to SeaQuery if necessaryI started with SQLx, then tried SeaQueryI found good examples on YouTube

Q. What qualities of SeaQL libraries do you think are important?โ€‹

Other: Simple SyntaxBeing able to easily express what you would otherwise be able to write in pure SQLMigration and entity generationClarify of the implementation and usage patternsEfficient query building especially with relations and joinsErgonomic API

Team & Project Natureโ€‹

Q. How many team members (including you) are working on the project?โ€‹

Q. Can you categorize the nature of the project?โ€‹

Other: ForecastingFinancial tradingEnterprise Resource Planning (ERP)FintechCloud infrstructure automationBackend for desktop, mobile and web application

Tech Stackโ€‹

Q. What is your development environment?โ€‹

Linux Breakdownโ€‹

Windows Breakdownโ€‹

macOS Breakdownโ€‹

Q. Which database(s) do you use?โ€‹

Q. Which web framework are you using?โ€‹

Q. What is the deployment environment?โ€‹

Rust at Workโ€‹

Q. Are you using Rust at work?โ€‹

Q. Which industry your company is in?โ€‹

Vague description of the companyโ€‹

A banking companyA business to business lending platformA cloud StorageA consulting companyA cybersecurity management platformAn IT solution companyAn E-Commerce clothing storeA children entertainmets companyA factory construction management platformA fintech startupA geology technology companyA publicly traded health-tech companyA private restaurant chainAn industrial IoT for heating and water distributionsAn internet providerA nonprofit tech research organizationA payment service providerA road intelligence companyA SaaS startupA server hosting providerA DevOps platform that helps our users scale their Kubernetes applicationAn Automotive company

Q. What is the size of your company?โ€‹

Q. How many engineers in your company are dedicated to writing Rust?โ€‹

Q. Which layer(s) of the technology stack are using Rust?โ€‹

Learning Rustโ€‹

Q. Are you learning / new to Rust?โ€‹

Q. Which language(s) are you most familiar with?โ€‹

Q. Are you familiar with SQL?โ€‹

Q. Do you find Rust easy or hard to learn?โ€‹

Q. What motivates you to learn Rust?โ€‹

Other: Ability to develop fast, secure and standalone API driven toolsEfficiency, safety, low resource usageGood design decisions from the startReliability and ease of developmentSchool makes me to learnRust is too coolThe ecosystem of libraries + general competence of lib authorsIt is the most loved languageThe guarantees Rust providesLearning something newType safety and speedWant to get away from NULLNo boilerplate, if you do not want itPerformance

Q. What learning resources do you rely on?โ€‹

Other: YouTubeOnline CoursesChatGPT

Q. What is your first project built using Rust?โ€‹

Other: ChatbotScraperRasterization of the mandelbrot setIoTLibrary

What's Nextโ€‹

Q. Which aspects do you want to see advancement on SeaORM?โ€‹

Thank you for all the suggestions, we will certainly take them into account!

Other: Full MySQL coverageMS SQL Server supportStructured queries for complex joinsA stable releaseData seedingMigrations based on Entity diffsType safetySupport tables without primary keyTurso integrationFetching nested structuresViews

Q. What tools would you be interested in using, if developed first-party by SeaQL?โ€‹

Other: An API integration testing utilityAn oso-based authorization integrationA visual tool for managing migrationsDatabase layout editor (like dbdiagram.io)

Share Your Thoughtsโ€‹

Q. Anything else you want to say?โ€‹

Didn't expect this section to turn into a testimonial, thank you for all the kind words :)

Good job yall

Great projects, thanks for your hard work

I expect it to be an asynchronous type-safe library. Keep up the good work!

I'd like to see entity generation without a database

The website, support from JetBrains, the documentation and the release cycle are very nice!

I'm very interested in how SeaORM will continue evolving and I would like to wish you the best of luck!

I've found SeaORM very useful and I'm very grateful to the development team for creating and maintaining it!

In TypeORM I can write entities and then generate migration from them. It's very handy. It helps to increase development speed. It would be nice to have this functionality in SeaORM.

It needs to have better integration with SeaQuery, I sometimes need to get to it because not all features are available in SeaORM which makes it a pain.

Keep the good work!

Keep going! Love SeaORM!

Keep up the great work. Rust needs a fast, ergonomic and reliable ORM.

SeaORM is very powerful, but the rust docs and tutorial examples could be more fleshed out.

SeaORM is an awesome library. Most things are quite concise and therefore straightforward. Simply a few edge cases concerning DB specific types and values could be better.

The trait system is too complex and coding experience is not pretty well with that.

Automatic migration generation would make the library pretty much perfect in my opinion.

SeaQL tutorials could be better. Much more detailed explanation and definitely it has to have best practices section for Design Patterns like and good best practices related with clean architecture.

SeaQL are great products and itโ€™s very enjoyable using them

Thank you <3

Thank you for awesome library!

Thank you for this wonderful project. I feel the documentation lacks examples for small functions and usage of some obscure features.

Thank you for your hard work!

Thank you for your work on SeaQL, your efforts are appreciated

Thank you for your work, we are seeking actively to include SeaORM in our projects

Thank you very much for your work!

Thanks a lot for the amazing work you guys put into this set of libraries. This is an amazing development for the rust ecosystem.

Thanks and keep up the good work.

Thanks for a great tool!

Thanks for all the amazing work.

Thanks for making SeaORM!

The project I am doing for work is only a prototype, it's a new implementation of a current Python forecasting project which uses a pandas and a custom psycopg2 orm. My intent is to create a faster/dev friendly version with SeaORM and Polars. I am hoping to eventually get a prototype I can display to my team to get a go ahead to fully develop a new version, and to migrate 4-5 other forecasting apps using shared libraries for io and calculations.

I have also been using SeaORM for a small API client for financial data, which I may make open source.

I think one thing which could really improve SeaORM is some more advance examples in the documentation section. The docs are really detailed as far as rust documentation goes.

Very promising project, keep it up.

Thank you so much for taking it upon yourselves to selflessly give your free time. It probably doesn't matter much, but thank you so much for your work. SeaORM is a fantastic tool that I can see myself using for a long time to come. I hope to make contributions in any form when I am under better circumstances :3 Kudos to the team!

ไฝ ไปฌ็š„ๅบ“้žๅธธ็š„ๆฃ’๏ผŒ่‡ณๅฐ‘ๆˆ‘่ง‰ๅพ—ๆฏ”Dieselๅฅฝๅคชๅคšไบ†๏ผŒๅ…ฅ้—จ็ฎ€ๅ•๏ผŒๅฏนๆ–ฐๆ‰‹้žๅธธๅ‹ๅฅฝ๏ผŒ่ฟ™ๆ˜ฏๆœ€ๅคง็š„ไบฎ็‚น๏ผŒๅ…ถๆฌกๆ˜ฏๅบ“่ฒŒไผผๅฏไปฅๅฎž็Žฐๅพˆๅคๆ‚็š„Join SQL้€ป่พ‘่€Œไธ็”จๅ†™ๅŽŸ็”Ÿ็š„SQL๏ผŒ่ฟ™็‚นไนŸๆ˜ฏ้žๅธธๅ€ผๅพ—็‚น่ตž็š„๏ผŒไฝ†ๆ˜ฏๅœจ่ฟ™ๅ—็š„ๆ–‡ๆกฃ่ฒŒไผผๅ†™็š„ๆœ‰็‚น็ฎ€็•ฅไบ†๏ผŒๅธŒๆœ›ๅฏไปฅไธฐๅฏŒไธ€ไธ‹ๆ–‡ๆกฃๅ†…ๅฎน๏ผŒๅฏนไบŽๅคๆ‚ๆŸฅ่ฏข็š„่ฏดๆ˜Žๅฏไปฅๆ›ดๅŠ ่ฏฆ็ป†ไธ€ไบ›๏ผŒ่ฟ™ๆ ทๅฐฑๅ†ๅฅฝไธ่ฟ‡ไบ†ใ€‚่ฐข่ฐขไฝ ไปฌ๏ผŒๆˆ‘ไผšๆŒ็ปญๅ…ณๆณจไฝ ไปฌ๏ผŒๆœชๆฅ็š„้กน็›ฎๅฆ‚ๆžœๆถ‰ๅŠORM๏ผŒ้‚ฃ็ปๅฏน้žไฝ ไปฌ่Žซๅฑžไบ†๏ผ

Rustacean Sticker Pack ๐Ÿฆ€โ€‹

The Rustacean Sticker Pack is the perfect way to express your passion for Rust. @@ -23,7 +23,7 @@ Stick them on your laptop, notebook, or any gadget to show off your love for Rust!

Moreover, all proceeds contributes directly to the ongoing development of SeaQL projects.

Sticker Pack Contents:

  • Logo of SeaQL projects: SeaQL, SeaORM, SeaQuery, Seaography, FireDBG
  • Mascot of SeaQL: Terres the Hermit Crab
  • Mascot of Rust: Ferris the Crab
  • The Rustacean word

Support SeaQL and get a Sticker Pack!

Rustacean Sticker Pack by SeaQL

ยท One min read
SeaQL Team

It is our honour to have been awarded by OpenUK for the 2023 Award in the Software category! The award ceremony was a very memorable experience. A huge thanks to Red Badger who sponsored the software award.

In 2023, we released SeaStreamer, two major versions of SeaORM, a new version of Seaography, and have been busy working on a new project on the side.

We reached the milestone of 5k GitHub stars and 2M crates.io downloads mid-year.

In the summer, we took in two interns for our 3rd summer of code.

We plan to offer internships tailored to UK students in 2024 through university internship programs. As always, we welcome contributors from all over the world, and may be we will enrol on GSoC 2024 again. (but open-source is not bounded any schedule, so you can start contributing anytime)

A big thanks to our sponsors who continued to support us, and we look forward to a more impactful 2024.

ยท 4 min read
Chris Tsang

If you are writing an async application in Rust, at some point you'd want to separate the code into several crates. There are some benefits:

  1. Better encapsulation. Having a crate boundary between sub-systems can lead to cleaner code and a more well-defined API. No more pub(crate)!
  2. Faster compilation. By breaking down a big crate into several independent small crates, they can be compiled concurrently.

But the question is, if you are using only one async runtime anyway, what are the benefits of writing async-runtime-generic libraries?

  1. Portability. You can easily switch to a different async runtime, or wasm.
  2. Correctness. Testing a library against both tokio and async-std can uncover more bugs, including concurrency bugs (due to fuzzy task execution orders) and "undefined behaviour" either due to misunderstanding or async-runtime implementation details

So now you've decided to write async-runtime-generic libraries! Here I want to share 3 strategies along with examples found in the Rust ecosystem.

Approach 1: Defining your own AsyncRuntime traitโ€‹

Using the futures crate you can write very generic library code, but there is one missing piece: time - to sleep or timeout, you have to rely on an async runtime. If that's all you need, you can define your own AsyncRuntime trait and requires downstream to implement it. This is the approach used by rdkafka:

pub trait AsyncRuntime: Send + Sync + 'static {
type Delay: Future<Output = ()> + Send;

/// It basically means the return value must be a `Future`
fn sleep(duration: Duration) -> Self::Delay;
}

Here is how it's implemented:

impl AsyncRuntime for TokioRuntime {
type Delay = tokio::time::Sleep;

fn sleep(duration: Duration) -> Self::Delay {
tokio::time::sleep(duration)
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>() {
R::sleep(Duration::from_millis(1)).await;
}

Approach 2: Abstract the async runtimes internally and expose feature flagsโ€‹

This is the approach used by redis-rs.

To work with network connections or file handle, you can use the AsyncRead / AsyncWrite traits:

#[async_trait]
pub(crate) trait AsyncRuntime: Send + Sync + 'static {
type Connection: AsyncRead + AsyncWrite + Send + Sync + 'static;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection>;
}

Then you'll define a module for each async runtime:

#[cfg(feature = "runtime-async-std")]
mod async_std_impl;
#[cfg(feature = "runtime-async-std")]
use async_std_impl::*;

#[cfg(feature = "runtime-tokio")]
mod tokio_impl;
#[cfg(feature = "runtime-tokio")]
use tokio_impl::*;

Where each module would look like:

tokio_impl.rs
#[async_trait]
impl AsyncRuntime for TokioRuntime {
type Connection = tokio::net::TcpStream;

async fn connect(addr: SocketAddr) -> std::io::Result<Self::Connection> {
tokio::net::TcpStream::connect(addr).await
}
}

Library code to use the above:

async fn operation<R: AsyncRuntime>(conn: R::Connection) {
conn.write(b"some bytes").await;
}

Approach 3: Maintain an async runtime abstraction crateโ€‹

This is the approach used by SQLx and SeaStreamer.

Basically, aggregate all async runtime APIs you'd use and write a wrapper library. This may be tedious, but this also has the benefit of specifying all interactions with the async runtime in one place for your project, which could be handy for debugging or tracing.

For example, async Task handling:

common-async-runtime/tokio_task.rs
pub use tokio::task::{JoinHandle as TaskHandle};

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
tokio::task::spawn(future)
}

async-std's task API is slightly different (in tokio the output is Result<T, JoinError>), which requires some boilerplate:

common-async-runtime/async_std_task.rs
/// A shim to match tokio's API
pub struct TaskHandle<T>(async_std::task::JoinHandle<T>);

pub fn spawn_task<F, T>(future: F) -> TaskHandle<T>
where
F: Future<Output = T> + Send + 'static,
T: Send + 'static,
{
TaskHandle(async_std::task::spawn(future))
}

#[derive(Debug)]
pub struct JoinError;

impl std::error::Error for JoinError {}

// This is basically how you wrap a `Future`
impl<T> Future for TaskHandle<T> {
type Output = Result<T, JoinError>;

fn poll(
mut self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Self::Output> {
match self.0.poll_unpin(cx) {
std::task::Poll::Ready(res) => std::task::Poll::Ready(Ok(res)),
std::task::Poll::Pending => std::task::Poll::Pending,
}
}
}

In the library's Cargo.toml, you can simply include common-async-runtime as dependency. This makes your library code 'pure', because now selecting an async runtime is controlled by downstream. Similar to approach 1, this crate can be compiled without any async runtime, which is neat!

Conclusionโ€‹

Happy hacking! Welcome to share your experience with the community.

ยท 5 min read
Chris Tsang

๐ŸŽ‰ We are pleased to release SeaStreamer 0.3.x!

File Backendโ€‹

A major addition in SeaStreamer 0.3 is the file backend. It implements the same high-level MPMC API, enabling streaming to and from files. There are different use cases. For example, it can be used to dump data from Redis / Kafka and process them locally, or as an intermediate file format for storage or transport.

The SeaStreamer File format, .ss is pretty simple. It's very much like .ndjson, but binary. The file format is designed with the following goals:

  1. Binary data support without encoding overheads
  2. Efficiency in rewinding / seeking through a large dump
  3. Streaming-friendliness - File can be truncated without losing integrity

Let me explain in details.

First of all, SeaStreamer File is a container format. It only concerns the message stream and framing, not the payload. It's designed to be paired with a binary message format like Protobuf or BSON.

Encode-freeโ€‹

JSON and CSV are great plain text file formats, but they are not binary friendly. Usually, to encode binary data, one would use base64. It therefore imposes an expensive encoding / decoding overhead. In a binary protocol, delimiters are frequently used to signal message boundaries. As a consequence, byte stuffing is needed to escape the bytes.

In SeaStreamer, we want to avoid the encoding overhead entirely. The payload should be written to disk verbatim. So the file format revolves around constructing message frames and placing checksums to ensure that data is interpreted correctly.

Efficient seekโ€‹

A delimiter-based protocol has an advantage: the byte stream can be randomly sought, and we always have no trouble reading the next message.

Since SeaStreamer does not rely on delimiters, we can't easily align to message frames after a random seek. We solve this problem by placing beacons in a regular interval at fixed locations throughout the file. E.g. say the beacon interval is 1024, there will be a beacon at the 1024th byte, the 2048th, and so on. Then, every time we want to seek to a random location, we'd seek to the closest N * 1024 byte and read from there.

These beacons also double as indices: they contain summaries of the individual streams. So given a particular stream key and sequence number (or timestamp) to search for, SeaStreamer can quickly locate the message just by reading the beacons. It doesn't matter if the stream's messages are sparse!

Streaming-friendlinessโ€‹

It should always be safe to truncate files. It should be relatively easy to split a file into chunks. We should be able to tell if the data is corrupted.

SeaStreamer achieves this by computing a checksum for every message, and also the running checksum of the checksums for each stream. It's not enforced right now, but in theory we can detect if any messages are missing from a stream.

Summaryโ€‹

This file format is also easy to implement in different languages, as we just made an (experimental) reader in Typescript.

That's it! If you are interested, you can go and take a look at the format description.

Redis Backendโ€‹

Redis Streams are underrated! They have high throughput and concurrency, and are best suited for non-persistent stream processing near or on the same host as the application.

The obstacle is probably in library support. Redis Streams' API is rather low level, and there aren't many high-level libraries to help with programming, as opposed to Kafka, which has versatile official programming libraries.

The pitfall is, it's not easy to maximize concurrency with the raw Redis API. To start, you'd need to pipeline XADD commands. You'd also need to time and batch XACKs so that it does not block reads and computation. And of course you want to separate the reads and writes on different threads.

SeaStreamer breaks these obstacles for you and offers a Kafka-like API experience!

Benchmarkโ€‹

In 0.3, we have done some optimizations to improve the throughput of the Redis and File backend. We set our initial benchmark at 100k messages per second, which hopefully we can further improve over time.

Our micro benchmark involves a simple program producing or consuming 100k messages, where each message has a payload of 256 bytes.

For Redis, it's running on the same computer in Docker. On my not-very-impressive laptop with a 10th Gen Intel Core i7, the numbers are somewhat around:

Producerโ€‹

redis    0.5s
stdio 0.5s
file 0.5s

Consumerโ€‹

redis    1.0s
stdio 1.0s
file 1.1s

It practically means that we are comfortably in the realm of producing 100k messages per second, but are just about able to consume 100k messages in 1 second. Suggestions to performance improvements are welcome!

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.

ยท 8 min read
SeaQL Team
SeaORM 0.12 Banner

๐ŸŽ‰ We are pleased to announce SeaORM 0.12 today!

We still remember the time when we first introduced SeaORM to the Rust community two years ago. We set out a goal to enable developers to build asynchronous database-driven applications in Rust.

Today, many open-source projects, a handful of startups and many more closed-source projects are using SeaORM. Thank you all who participated and contributed in the making!

SeaORM Star History

New Features ๐ŸŒŸโ€‹

๐Ÿงญ Seaography: GraphQL integration (preview)โ€‹

Seaography example

Seaography is a GraphQL framework built on top of SeaORM. In 0.12, Seaography integration is built into sea-orm. Seaography allows you to build GraphQL resolvers quickly. With just a few commands, you can launch a GraphQL server from SeaORM entities!

While Seaography development is still in an early stage, it is especially useful in prototyping and building internal-use admin panels.

Read the documentation to learn more.

Added macro DerivePartialModelโ€‹

#1597 Now you can easily perform custom select to query only the columns you needed

#[derive(DerivePartialModel, FromQueryResult)]
#[sea_orm(entity = "Cake")]
struct PartialCake {
name: String,
#[sea_orm(
from_expr = r#"SimpleExpr::FunctionCall(Func::upper(Expr::col((Cake, cake::Column::Name))))"#
)]
name_upper: String,
}

assert_eq!(
cake::Entity::find()
.into_partial_model::<PartialCake>()
.into_statement(DbBackend::Sqlite)
.to_string(),
r#"SELECT "cake"."name", UPPER("cake"."name") AS "name_upper" FROM "cake""#
);

Added Select::find_with_linkedโ€‹

#1728, #1743 Similar to find_with_related, you can now select related entities and consolidate the models.

// Consider the following link
pub struct BakedForCustomer;

impl Linked for BakedForCustomer {
type FromEntity = Entity;

type ToEntity = super::customer::Entity;

fn link(&self) -> Vec<RelationDef> {
vec![
super::cakes_bakers::Relation::Baker.def().rev(),
super::cakes_bakers::Relation::Cake.def(),
super::lineitem::Relation::Cake.def().rev(),
super::lineitem::Relation::Order.def(),
super::order::Relation::Customer.def(),
]
}
}

let res: Vec<(baker::Model, Vec<customer::Model>)> = Baker::find()
.find_with_linked(baker::BakedForCustomer)
.order_by_asc(baker::Column::Id)
.all(db)
.await?

Added DeriveValueType derive macro for custom wrapper typesโ€‹

#1720 So now you can use newtypes easily.

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "custom_value_type")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub number: Integer,
// Postgres only
pub str_vec: StringVec,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct Integer(i32);

#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct StringVec(pub Vec<String>);

Which saves you the boilerplate of:

impl std::convert::From<StringVec> for Value { .. }

impl TryGetable for StringVec {
fn try_get_by<I: ColIdx>(res: &QueryResult, idx: I)
-> Result<Self, TryGetError> { .. }
}

impl ValueType for StringVec {
fn try_from(v: Value) -> Result<Self, ValueTypeErr> { .. }

fn type_name() -> String { "StringVec".to_owned() }

fn array_type() -> ArrayType { ArrayType::String }

fn column_type() -> ColumnType { ColumnType::String(None) }
}

Enhancements ๐Ÿ†™โ€‹

#1433 Chained AND / OR join ON conditionโ€‹

Added more macro attributes to DeriveRelation

// Entity file

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
// By default, it's `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` AND `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
)]
TropicalFruit,
// Specify `condition_type = "any"` to override it, now it becomes
// `JOIN `fruit` ON `cake`.`id` = `fruit`.`cake_id` OR `fruit`.`name` LIKE '%tropical%'`
#[sea_orm(
has_many = "super::fruit::Entity",
on_condition = r#"super::fruit::Column::Name.like("%tropical%")"#
condition_type = "any",
)]
OrTropicalFruit,
}

#1508 Supports entity with composite primary key of arity 12โ€‹

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "primary_key_of_12")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id_1: String,
...
#[sea_orm(primary_key, auto_increment = false)]
pub id_12: bool,
}

#1677 Added UpdateMany::exec_with_returning()โ€‹

let models: Vec<Model> = Entity::update_many()
.col_expr(Column::Values, Expr::expr(..))
.exec_with_returning(db)
.await?;

#1511 Added MigratorTrait::migration_table_name() method to configure the name of migration tableโ€‹

#[async_trait::async_trait]
impl MigratorTrait for Migrator {
// Override the name of migration table
fn migration_table_name() -> sea_orm::DynIden {
Alias::new("override_migration_table_name").into_iden()
}
...
}

#1707 Added DbErr::sql_err() method to parse common database errorsโ€‹

assert!(matches!(
cake.into_active_model().insert(db).await
.expect_err("Insert a row with duplicated primary key")
.sql_err(),
Some(SqlErr::UniqueConstraintViolation(_))
));

assert!(matches!(
fk_cake.insert(db).await
.expect_err("Insert a row with invalid foreign key")
.sql_err(),
Some(SqlErr::ForeignKeyConstraintViolation(_))
));

#1737 Introduced new ConnAcquireErrโ€‹

enum DbErr {
ConnectionAcquire(ConnAcquireErr),
..
}

enum ConnAcquireErr {
Timeout,
ConnectionClosed,
}

#1627 Added DatabaseConnection::ping()โ€‹

|db: DatabaseConnection| {
assert!(db.ping().await.is_ok());
db.clone().close().await;
assert!(matches!(db.ping().await, Err(DbErr::ConnectionAcquire)));
}

#1708 Added TryInsert that does not panic on empty insertsโ€‹

// now, you can do:
let res = Bakery::insert_many(std::iter::empty())
.on_empty_do_nothing()
.exec(db)
.await;

assert!(matches!(res, Ok(TryInsertResult::Empty)));

#1712 Insert on conflict do nothing to return Okโ€‹

let on = OnConflict::column(Column::Id).do_nothing().to_owned();

// Existing behaviour
let res = Entity::insert_many([..]).on_conflict(on).exec(db).await;
assert!(matches!(res, Err(DbErr::RecordNotInserted)));

// New API; now you can:
let res =
Entity::insert_many([..]).on_conflict(on).do_nothing().exec(db).await;
assert!(matches!(res, Ok(TryInsertResult::Conflicted)));

#1740, #1755 Replacing sea_query::Iden with sea_orm::DeriveIdenโ€‹

To provide a more consistent interface, sea-query/derive is no longer enabled by sea-orm, as such, Iden no longer works as a derive macro (it's still a trait).

// then:

#[derive(Iden)]
#[iden = "category"]
pub struct CategoryEnum;

#[derive(Iden)]
pub enum Tea {
Table,
#[iden = "AfternoonTea"]
EverydayTea,
}

// now:

#[derive(DeriveIden)]
#[sea_orm(iden = "category")]
pub struct CategoryEnum;

#[derive(DeriveIden)]
pub enum Tea {
Table,
#[sea_orm(iden = "AfternoonTea")]
EverydayTea,
}

New Release Train Ferry ๐Ÿšขโ€‹

It's been the 12th release of SeaORM! Initially, a major version was released every month. It gradually became 2 to 3 months, and now, it's been 6 months since the last major release. As our userbase grew and some are already using SeaORM in production, we understand the importance of having a stable API surface and feature set.

That's why we are committed to:

  1. Reviewing breaking changes with strict scrutiny
  2. Expanding our test suite to cover all features of our library
  3. Never remove features, and consider deprecation carefully

Today, the architecture of SeaORM is pretty solid and stable, and with the 0.12 release where we paid back a lot of technical debt, we will be able to deliver new features and enhancements without breaking. As our major dependency SQLx is not 1.0 yet, technically we cannot be 1.0.

We are still advancing rapidly, and we will always make a new release as soon as SQLx makes a new release, so that you can upgrade everything at once. As a result, the next major release of SeaORM will come out 6 months from now, or when SQLx makes a new release, whichever is earlier.

Community Survey ๐Ÿ“โ€‹

SeaQL is an independent open-source organization. Our goal is to enable developers to build data intensive applications in Rust. If you are using SeaORM, please participate in the SeaQL Community Survey!

By completing this survey, you will help us gather insights into how you, the developer, are using our libraries and identify means to improve your developer experience. We will also publish an annual survey report to summarize our findings.

If you are a happy user of SeaORM, consider writing us a testimonial!

A big thank to DigitalOcean who sponsored our server hosting, and JetBrains who sponsored our IDE, and every sponsor on GitHub Sponsor!

If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the organization.

A big shout out to our sponsors ๐Ÿ˜‡:

Shane Sveller
ร‰mile Fugulin
Afonso Barracha
Jacob Trueb
Natsuki Ikeguchi
Marlon Mueller-Soppart
KallyDev
Dean Sheather
Manfred Lee
Roland Gorรกcz
IceApinan
Renรฉ Klaฤan
Unnamed Sponsor

What's Next for SeaORM? โ›ตโ€‹

Open-source project is a never-ending work, and we are actively looking for ways to sustain the project. You can support our endeavour by starring & sharing our repositories and becoming a sponsor.

We are considering multiple directions to generate revenue for the organization. If you have any suggestion, or want to join or collaborate with us, please contact us via hello[at]sea-ql.org.

Thank you for your support, and together we can make open-source sustainable.

ยท 5 min read
Chris Tsang

We are pleased to introduce SeaStreamer to the Rust community today. SeaStreamer is a stream processing toolkit to help you build stream processors in Rust.

At SeaQL we want to make Rust the best programming platform for data engineering. Where SeaORM is the essential tool for working with SQL databases, SeaStreamer aims to be your essential toolkit for working with streams.

Currently SeaStreamer provides integration with Kafka and Redis.

Let's have a quick tour of SeaStreamer.

High level async APIโ€‹

  • High level async API that supports both async-std and tokio
  • Mutex-free implementation1: concurrency achieved by message passing
  • A comprehensive type system that guides/restricts you with the API

Below is a basic Kafka consumer:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "kafka://streamer.sea-ql.org:9092/my_stream".parse()?;
let streamer = KafkaStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = KafkaConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_offset_reset(AutoOffsetReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Consumer::stream() returns an object that implements the Stream trait, which allows you to do neat things:

let items = consumer
.stream()
.take(num)
.map(process_message)
.collect::<Vec<_>>()
.await

Trait-based abstract interfaceโ€‹

All SeaStreamer backends implement a common abstract interface, offering you a familiar API. Below is a basic Redis consumer, which is nearly the same as the previous example:

#[tokio::main]
async fn main() -> Result<()> {
env_logger::init();

let stream: StreamUrl = "redis://localhost:6379/my_stream".parse()?;
let streamer = RedisStreamer::connect(stream.streamer(), Default::default()).await?;
let mut options = RedisConsumerOptions::new(ConsumerMode::RealTime);
options.set_auto_stream_reset(AutoStreamReset::Earliest);
let consumer = streamer
.create_consumer(stream.stream_keys(), options)
.await?;

loop {
let mess = consumer.next().await?;
println!("{}", mess.message().as_str()?);
}
}

Redis Streams Supportโ€‹

SeaStreamer Redis provides a Kafka-like stream semantics:

  • Non-group streaming with AutoStreamReset option
  • Consumer-group-based streaming with auto-ack and/or auto-commit
  • Load balancing among consumers with automatic failover
  • Seek/rewind to point in time

You don't have to call XADD, XREAD, XACK, etc... anymore!

Enum-based generic interfaceโ€‹

The trait-based API requires you to designate the concrete Streamer type for monomorphization, otherwise the code cannot compile.

Akin to how SeaORM implements runtime-polymorphism, SeaStreamer provides a enum-based generic streamer, in which the backend is selected on runtime.

Here is an illustration (full example):

// sea-streamer-socket
pub struct SeaConsumer {
backend: SeaConsumerBackend,
}

enum SeaConsumerBackend {
#[cfg(feature = "backend-kafka")]
Kafka(KafkaConsumer),
#[cfg(feature = "backend-redis")]
Redis(RedisConsumer),
#[cfg(feature = "backend-stdio")]
Stdio(StdioConsumer),
}

// Your code
let uri: StreamerUri = "kafka://localhost:9092".parse()?; // or
let uri: StreamerUri = "redis://localhost:6379".parse()?; // or
let uri: StreamerUri = "stdio://".parse()?;

// SeaStreamer will be backed by Kafka, Redis or Stdio depending on the URI
let streamer = SeaStreamer::connect(uri, Default::default()).await?;

// Set backend-specific options
let mut options = SeaConsumerOptions::new(ConsumerMode::Resumable);
options.set_kafka_consumer_options(|options: &mut KafkaConsumerOptions| { .. });
options.set_redis_consumer_options(|options: &mut RedisConsumerOptions| { .. });
let mut consumer: SeaConsumer = streamer.create_consumer(stream_keys, options).await?;

// You can still retrieve the concrete type
let kafka: Option<&mut KafkaConsumer> = consumer.get_kafka();
let redis: Option<&mut RedisConsumer> = consumer.get_redis();

So you can "write once, stream anywhere"!

Good old unix pipeโ€‹

In SeaStreamer, stdin & stdout can be used as stream source and sink.

Say you are developing some processors to transform a stream in several stages:

./processor_1 --input kafka://localhost:9092/input --output kafka://localhost:9092/stage_1 &
./processor_2 --input kafka://localhost:9092/stage_1 --output kafka://localhost:9092/stage_2 &
./processor_3 --input kafka://localhost:9092/stage_2 --output kafka://localhost:9092/output &

It would be great if we can simply pipe the processors together right?

With SeaStreamer, you can do the following:

./processor_1 --input kafka://localhost:9092/input --output stdio:///stream |
./processor_2 --input stdio:///stream --output stdio:///stream |
./processor_3 --input stdio:///stream --output kafka://localhost:9092/output

All without recompiling the stream processors! Now, you can develop locally with the comfort of using |, >, < and your favourite unix program in the shell.

Testableโ€‹

SeaStreamer encourages you to write tests at all levels:

  • You can execute tests involving several stream processors in the same OS process
  • You can execute tests involving several OS processes by connecting them with pipes
  • You can execute tests involving several stream processors with Redis / Kafka

All against the same piece of code! Let SeaStreamer take away the boilerplate and mocking facility from your codebase.

Below is an example of intra-process testing, which can be run with cargo test without any dependency or side-effects:

let stream = StreamKey::new("test")?;
let mut options = StdioConnectOptions::default();
options.set_loopback(true); // messages produced will be feed back to consumers
let streamer = StdioStreamer::connect(StreamerUri::zero(), options).await?;
let producer = streamer.create_producer(stream.clone(), Default::default()).await?;
let mut consumer = streamer.create_consumer(&[stream.clone()], Default::default()).await?;

for i in 0..5 {
let mess = format!("{}", i);
producer.send(mess)?;
}

let seq = collect(&mut consumer, 5).await;
assert_eq!(seq, [0, 1, 2, 3, 4]);

Getting startedโ€‹

If you are eager to get started with SeaStreamer, you can checkout our set of examples:

  • consumer: A basic consumer
  • producer: A basic producer
  • processor: A basic stream processor
  • resumable: A resumable stream processor that continues from where it left off
  • buffered: An advanced stream processor with internal buffering and batch processing
  • blocking: An advanced stream processor for handling blocking / CPU-bound tasks

Read the official documentation to learn more.

Roadmapโ€‹

A few major components we plan to develop:

  • File Backend
  • Redis Cluster

We welcome you to join our Discussions if you have thoughts or ideas!

Peopleโ€‹

SeaStreamer is designed and developed by the same mind who brought you SeaORM:

Chris Tsang

Communityโ€‹

SeaQL.org is an independent open-source organization run by passionate ๏ธdevelopers. If you like our projects, please star โญ and share our repositories. If you feel generous, a small donation via GitHub Sponsor will be greatly appreciated, and goes a long way towards sustaining the organization ๐Ÿšข.

SeaStreamer is a community driven project. We welcome you to participate, contribute and together build for Rust's future ๐Ÿฆ€.


  1. except sea-streamer-stdio, but only contends on consumer add/dropโ†ฉ

ยท 10 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.11.0!

Data Loaderโ€‹

[#1443, #1238] The LoaderTrait provides an API to load related entities in batches.

Consider this one to many relation:

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

The generated SQL is:

SELECT
"cake"."id" AS "A_id",
"cake"."name" AS "A_name",
"fruit"."id" AS "B_id",
"fruit"."name" AS "B_name",
"fruit"."cake_id" AS "B_cake_id"
FROM "cake"
LEFT JOIN "fruit" ON "cake"."id" = "fruit"."cake_id"
ORDER BY "cake"."id" ASC

The 1 side's (Cake) data will be duplicated. If N is a large number, this would results in more data being transferred over the wire. Using the Loader would ensure each model is transferred only once.

The following loads the same data as above, but with two queries:

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

for (cake, fruits) in cakes.into_iter().zip(fruits.into_iter()) { .. }
SELECT "cake"."id", "cake"."name" FROM "cake"
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit" WHERE "fruit"."cake_id" IN (..)

You can even apply filters on the related entity:

let fruits_in_stock: Vec<Vec<fruit::Model>> = cakes.load_many(
fruit::Entity::find().filter(fruit::Column::Stock.gt(0i32))
db
).await?;
SELECT "fruit"."id", "fruit"."name", "fruit"."cake_id" FROM "fruit"
WHERE "fruit"."stock" > 0 AND "fruit"."cake_id" IN (..)

To learn more, read the relation docs.

Transaction Isolation Level and Access Modeโ€‹

[#1230] The transaction_with_config and begin_with_config allows you to specify the IsolationLevel and AccessMode.

For now, they are only implemented for MySQL and Postgres. In order to align their semantic difference, MySQL will execute SET TRANSACTION commands before begin transaction, while Postgres will execute SET TRANSACTION commands after begin transaction.

db.transaction_with_config::<_, _, DbErr>(
|txn| { ... },
Some(IsolationLevel::ReadCommitted),
Some(AccessMode::ReadOnly),
)
.await?;

let transaction = db
.begin_with_config(IsolationLevel::ReadCommitted, AccessMode::ReadOnly)
.await?;

To learn more, read the transaction docs.

Cast Column Type on Select and Saveโ€‹

[#1304] If you need to select a column as one type but save it into the database as another, you can specify the select_as and the save_as attributes to perform the casting. A typical use case is selecting a column of type citext (case-insensitive text) as String in Rust and saving it into the database as citext. One should define the model field as below:

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "ci_table")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(select_as = "text", save_as = "citext")]
pub case_insensitive_text: String
}

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

impl ActiveModelBehavior for ActiveModel {}

Changes to ActiveModelBehaviorโ€‹

[#1328, #1145] The methods of ActiveModelBehavior now have Connection as an additional parameter. It enables you to perform database operations, for example, logging the changes made to the existing model or validating the data before inserting it.

#[async_trait]
impl ActiveModelBehavior for ActiveModel {
/// Create a new ActiveModel with default values. Also used by `Default::default()`.
fn new() -> Self {
Self {
uuid: Set(Uuid::new_v4()),
..ActiveModelTrait::default()
}
}

/// Will be triggered before insert / update
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// Logging changes
edit_log::ActiveModel {
action: Set("before_save".into()),
values: Set(serde_json::json!(model)),
..Default::default()
}
.insert(db)
.await?;

Ok(self)
}
}

To learn more, read the entity docs.

Execute Unprepared SQL Statementโ€‹

[#1327] You can execute an unprepared SQL statement with ConnectionTrait::execute_unprepared.

// Use `execute_unprepared` if the SQL statement doesn't have value bindings
db.execute_unprepared(
"CREATE TABLE `cake` (
`id` int NOT NULL AUTO_INCREMENT PRIMARY KEY,
`name` varchar(255) NOT NULL
)"
)
.await?;

// Construct a `Statement` if the SQL contains value bindings
let stmt = Statement::from_sql_and_values(
manager.get_database_backend(),
r#"INSERT INTO `cake` (`name`) VALUES (?)"#,
["Cheese Cake".into()]
);
db.execute(stmt).await?;

Select Into Tupleโ€‹

[#1311] You can select a tuple (or single value) with the into_tuple method.

let res: Vec<(String, i64)> = cake::Entity::find()
.select_only()
.column(cake::Column::Name)
.column(cake::Column::Id.count())
.group_by(cake::Column::Name)
.into_tuple()
.all(&db)
.await?;

Atomic Migrationโ€‹

[#1379] Migration will be executed in Postgres atomically that means migration scripts will be executed inside a transaction. Changes done to the database will be rolled back if the migration failed. However, atomic migration is not supported in MySQL and SQLite.

You can start a transaction inside each migration to perform operations like seeding sample data for a newly created table.

Types Supportโ€‹

  • [#1325] Support various UUID formats that are available in uuid::fmt module
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "uuid_fmt")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub uuid: Uuid,
pub uuid_braced: uuid::fmt::Braced,
pub uuid_hyphenated: uuid::fmt::Hyphenated,
pub uuid_simple: uuid::fmt::Simple,
pub uuid_urn: uuid::fmt::Urn,
}
  • [#1210] Support vector of enum for Postgres
#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "tea")]
pub enum Tea {
#[sea_orm(string_value = "EverydayTea")]
EverydayTea,
#[sea_orm(string_value = "BreakfastTea")]
BreakfastTea,
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_vec")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub teas: Vec<Tea>,
pub teas_opt: Option<Vec<Tea>>,
}
  • [#1414] Support ActiveEnum field as primary key
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "enum_primary_key")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub id: Tea,
pub category: Option<Category>,
pub color: Option<Color>,
}

Opt-in Unstable Internal APIsโ€‹

By enabling sea-orm-internal feature you opt-in unstable internal APIs including:

Breaking Changesโ€‹

  • [#1366] sea-query has been upgraded to 0.28.x, which comes with some improvements and breaking changes. Please follow the release notes for more details

  • [#1420] sea-orm-cli: generate entity command enable --universal-time flag by default

  • [#1425] Added RecordNotInserted and RecordNotUpdated to DbErr

  • [#1327] Added ConnectionTrait::execute_unprepared method

  • [#1311] The required method of TryGetable changed:

// then
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError>;
// now; ColIdx can be `&str` or `usize`
fn try_get_by<I: ColIdx>(res: &QueryResult, index: I) -> Result<Self, TryGetError>;

So if you implemented it yourself:

impl TryGetable for XXX {
- fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, TryGetError> {
+ fn try_get_by<I: sea_orm::ColIdx>(res: &QueryResult, idx: I) -> Result<Self, TryGetError> {
- let value: YYY = res.try_get(pre, col).map_err(TryGetError::DbErr)?;
+ let value: YYY = res.try_get_by(idx).map_err(TryGetError::DbErr)?;
..
}
}
  • [#1328] The ActiveModelBehavior trait becomes async trait. If you overridden the default ActiveModelBehavior implementation:
#[async_trait::async_trait]
impl ActiveModelBehavior for ActiveModel {
async fn before_save<C>(self, db: &C, insert: bool) -> Result<Self, DbErr>
where
C: ConnectionTrait,
{
// ...
}

// ...
}
  • [#1425] DbErr::RecordNotFound("None of the database rows are affected") is moved to a dedicated error variant DbErr::RecordNotUpdated
let res = Update::one(cake::ActiveModel {
name: Set("Cheese Cake".to_owned()),
..model.into_active_model()
})
.exec(&db)
.await;

// then
assert_eq!(
res,
Err(DbErr::RecordNotFound(
"None of the database rows are affected".to_owned()
))
);

// now
assert_eq!(res, Err(DbErr::RecordNotUpdated));
  • [#1395] sea_orm::ColumnType was replaced by sea_query::ColumnType
    • Method ColumnType::def was moved to ColumnTypeTrait
    • ColumnType::Binary becomes a tuple variant which takes in additional option sea_query::BlobSize
    • ColumnType::Custom takes a sea_query::DynIden instead of String and thus a new method custom is added (note the lowercase)
// Compact Entity
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "fruit")]
pub struct Model {
- #[sea_orm(column_type = r#"Custom("citext".to_owned())"#)]
+ #[sea_orm(column_type = r#"custom("citext")"#)]
pub column: String,
}
// Expanded Entity
impl ColumnTrait for Column {
type EntityName = Entity;

fn def(&self) -> ColumnDef {
match self {
- Self::Column => ColumnType::Custom("citext".to_owned()).def(),
+ Self::Column => ColumnType::custom("citext").def(),
}
}
}

SeaORM Enhancementsโ€‹

  • [#1256] Refactor schema module to expose functions for database alteration
  • [#1346] Generate compact entity with #[sea_orm(column_type = "JsonBinary")] macro attribute
  • MockDatabase::append_exec_results(), MockDatabase::append_query_results(), MockDatabase::append_exec_errors() and MockDatabase::append_query_errors() [#1367] take any types implemented IntoIterator trait
  • [#1362] find_by_id and delete_by_id take any Into primary key value
  • [#1410] QuerySelect::offset and QuerySelect::limit takes in Into<Option<u64>> where None would reset them
  • [#1236] Added DatabaseConnection::close
  • [#1381] Added is_null getter for ColumnDef
  • [#1177] Added ActiveValue::reset to convert Unchanged into Set
  • [#1415] Added QueryTrait::apply_if to optionally apply a filter
  • Added the sea-orm-internal feature flag to expose some SQLx types
    • [#1297] Added DatabaseConnection::get_*_connection_pool() for accessing the inner SQLx connection pool
    • [#1434] Re-exporting SQLx errors

CLI Enhancementsโ€‹

  • [#846, #1186, #1318] Generate #[serde(skip_deserializing)] for primary key columns
  • [#1171, #1320] Generate #[serde(skip)] for hidden columns
  • [#1124, #1321] Generate entity with extra derives and attributes for model struct

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

Our GitHub Sponsor profile is up! SeaQL.org is an independent open-source organization run by passionate developers. If you enjoy using SeaORM, please star and share our repositories. If you feel generous, a small donation will be greatly appreciated, and goes a long way towards sustaining the project.

A big shout out to our sponsors ๐Ÿ˜‡:

Afonso Barracha
ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Nick Price
Roland Gorรกcz
Henrik Giesel
Jacob Trueb
Naoki Ikeguchi
Manfred Lee
Marcus Buffett
efrain2007

What's Next?โ€‹

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

Here is the roadmap for SeaORM 0.12.x.

ยท 2 min read
Chris Tsang

FAQ.02 Why the empty enum Relation {} is needed even if an Entity has no relations?โ€‹

Consider the following example Post Entity:

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

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "posts")]
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 {}

The two lines for defining Relation is quite unnecessary right?

To explain the problem, let's dive slightly deeper into the macro-expanded entity:

The DeriveRelation macro simply implements the RelationTrait:

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
match self {
_ => unreachable!()
}
}
}

Which in turn is needed by EntityTrait as an associated type:

impl EntityTrait for Entity {
type Relation = Relation;
...
}

It would be ideal if, when the user does not specify this associated type, the library automatically fills in a stub to satisfy the type system?

Turns out, there is such a feature in Rust! It is an unstable feature called associated_type_defaults.

Basically, it allows trait definitions to specify a default associated type, allowing it to be elided:

// only compiles in nightly
trait EntityTrait {
type Relation: Relation = EmptyRelation;
}

Due to our commitment to stable Rust, this may not land in SeaORM very soon. When it is stabilized, do remind us to implement this feature to get rid of those two lines!

ยท 4 min read
SeaQL Team

SeaQL.org offer internships tailored to university students. In fact, it will be the 3rd cohort in 2023.

The internships normally take place during summer and winter semester breaks. During the internship period, you will work on a project dedicatedly and publish the projectโ€™s outcome at the end.

The striking aspect of our mode of operation is it covers the entire lifecycle of software development, from Design โžก๏ธ Implementation โžก๏ธ Testing โžก๏ธ Delivery. You will be amazed of how much you can achieve in such a short period of time!

To date StarfishQL โœด๏ธ and Seaography ๐Ÿงญ are great projects our team has created in the past year. We pride ourselves on careful planning, consistent execution, and pragmatic approach in software engineering. I spend a huge amount of time on idea evaluation: if the scope of the project is too small, it will be uninteresting, but if it is too large, it will fail to be delivered.

Fellow undergraduates, here are a few good reasons why you should participate in internships at an open-source organization like SeaQL:

  1. A tangible showcase on CV: open-source work is published, inspectable and has real-world impact. We will also ensure that it has good branding, graphics, and visibility.
  2. Not driven by a business process, we do not compromise on quality of work. We do not have a proprietary development process, so itโ€™s all open-source tools with transferable skills.
  3. You will contribute to the community and will interact with people across the world. Collaboration on open source is the best thing humanity ever invented. You will only believe me when you have experienced it first-hand.
  4. Because you are the driver of the project you work on, it allows you to uncover something more about yourself, in particular - abilities and discipline: you always have had under/over-estimated yourself in one aspect or another.

Here are several things you are going to learn:

  1. "Thinking > Programming": the more time you spend on thinking beforehand, the better the code you are going to write. And the more time you spend on reviewing afterwards, the better the code is going to be.
  2. How to organize a codebase. Make good use of the Rust type system to craft a modular, testable codebase.
  3. Test automation. Every SeaQL project is continuously tested, and this is an integral part of our engineering process.
  4. Documentation. Our software aims to provide good documentation that is comprehensive, easy to follow, and fun to read.
  5. Performance tuning. Depending on the project, we might do some benchmarking and optimization. But in general we put you off from writing code that creates unnecessary overhead.

We were a mentor organization in GSoC 2022 and may be in 2023 (update: we were not accepted into GSoC 2023). We also offer internships outside of GSoC. So, what are the requirements when you become a contributor?

  1. Be passionate. You must show your passion in open-source and software engineering, so a good GitHub profile with some participation is needed.
  2. Be dedicated. This is a full-time job. While being fully remote and flexible on hours, you must have no other commitment or duties during the stipulated internship period.
  3. Be open-minded. You should listen carefully to your mentors and act on their advice accordingly.
  4. Write more. Communicate your thoughts and progress on all channels in an organized manner.

Donโ€™t just listen to me though. Here is what our past interns says:

Be well-prepared for your upcoming career in this technology industry! Follow us on GitHub and Twitter now, and stay tuned for future announcements.

ยท 4 min read
Chris Tsang

We are calling for contributors and reviewers for SeaQL projects ๐Ÿ“ข!

The SeaQL userbase has been steadily growing in the past year, and itโ€™s a pleasure for us to have helped individuals and start-ups to build their projects in Rust. However, the volume of questions, issues and pull requests is nearly saturating our core membersโ€™ capacity.

But again, thank you everyone for participating in the community!

If your project depends on SeaQL and you want to help us, here are some suggestions (if you have not already, star all our repositories and follow us on Twitter):

  1. Financial Contribution. You can sponsor us on GitHub and those will be used to cover our expenses. As a courtesy, we listen to our sponsors for their needs and use cases, and we also communicate our organizational development from time-to-time.
  2. Code Contribution. Opening a PR with us is always appreciated! To get started, you can go through our issue trackers and pick one to handle. If you are thinking of developing a substantial feature, start with drafting a "Proposal & Implementation Plan" (PIP).
  3. Knowledge Contribution. There are various formats of knowledge sharing: tutorial, cookbook, QnA and Discord. You can open PRs to our documentation repositories or publish on your own. We will be happy to list it in our learning resources section. Keep an eye on our GitHub Discussions and Discord and help others where you can!
  4. Code Review. This is an important process of our engineering. Right now, only 3 of our core members serve as reviewers. Non-core members can also become reviewers and I invite you to become one!

Now, Iโ€™d like to outline our review policy: for maturing projects, each PR merged has to be approved by at least two reviewers and one of them must be a core member; self-review allowed. Here are some examples:

  • A core member opened a PR, another core member approved โœ…
  • A core member opened a PR, a reviewer approved โœ…
  • A reviewer opened a PR, a core member approved โœ…
  • A reviewer opened a PR, another reviewer approved โ›”
  • A contributor opened a PR, 2 core members approved โœ…
  • A contributor opened a PR, a core member and a reviewer approved โœ…
  • A contributor opened a PR, 2 reviewers approved โ›”

In a nutshell, at least two pairs of trusted eyes should have gone through each PR.

What are the criteria when reviewing a PR?โ€‹

The following questions should all be answered yes.

  1. Implementation, documentation and tests
    1. Is the implementation easy to follow (have meaningful variable and function names)?
    2. Is there sufficient document to the API?
    3. Are there adequate tests covering various cases?
  2. API design
    1. Is the API self-documenting so users can understand its use easily?
    2. Is the API style consistent with our existing API?
    3. Does the API made reasonable use of the type system to enforce constraints?
    4. Are the failure paths and error messages clear?
    5. Are all breaking changes justified and documented?
  3. Functionality
    1. Does the feature make sense in computer science terms?
    2. Does the feature actually work with all our supported backends?
    3. Are all caveats discussed and eliminated / documented?
  4. Architecture
    1. Does it fit with the existing architecture of our codebase?
    2. Is it not going to create technical debt / maintenance burden?
    3. Does it not break abstraction?

1, 2 & 3 are fairly objective and factual, however the answers to 4 probably require some discussion and debate. If a consensus cannot be made, @tyt2y3 will make the final verdict.

Who are the current reviewers?โ€‹

As of today, SeaQL has 3 core members who are also reviewers:

Chris Tsang
Founder. Maintains all projects.
Billy Chan
Founding member. Co-maintainer of SeaORM and Seaography.
Ivan Krivosheev
Joined in 2022. Co-maintainer of SeaQuery.

How to become a reviewer?โ€‹

We are going to invite a few contributors we worked closely with, but you can also volunteer โ€“ the requirement is: you have made substantial code contribution to our projects, and has shown familiarity with our engineering practices.

Over time, when you have made significant contribution to our organization, you can also become a core member.

Letโ€™s build for Rust's future together ๐Ÿฆ€โ€‹

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.28.0! Here are some feature highlights ๐ŸŒŸ:

New IdenStatic trait for static identifierโ€‹

[#508] Representing a identifier with &'static str. The IdenStatic trait looks like this:

pub trait IdenStatic: Iden + Copy + 'static {
fn as_str(&self) -> &'static str;
}

You can derive it easily for your existing Iden. Just changing the #[derive(Iden)] into #[derive(IdenStatic)].

#[derive(IdenStatic)]
enum User {
Table,
Id,
FirstName,
LastName,
#[iden = "_email"]
Email,
}

assert_eq!(User::Email.as_str(), "_email");

New PgExpr and SqliteExpr traits for backend specific expressionsโ€‹

[#519] Postgres specific and SQLite specific expressions are being moved into its corresponding trait. You need to import the trait into scope before construct the expression with those backend specific methods.

// Importing `PgExpr` trait before constructing Postgres expression
use sea_query::{extension::postgres::PgExpr, tests_cfg::*, *};

let query = Query::select()
.columns([Font::Name, Font::Variant, Font::Language])
.from(Font::Table)
.and_where(Expr::val("a").concatenate("b").concat("c").concat("d"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "name", "variant", "language" FROM "font" WHERE 'a' || 'b' || 'c' || 'd'"#
);
// Importing `SqliteExpr` trait before constructing SQLite expression
use sea_query::{extension::sqlite::SqliteExpr, tests_cfg::*, *};

let query = Query::select()
.column(Font::Name)
.from(Font::Table)
.and_where(Expr::col(Font::Name).matches("a"))
.to_owned();

assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"SELECT "name" FROM "font" WHERE "name" MATCH 'a'"#
);

Bug Fixesโ€‹

// given
let (statement, values) = sea_query::Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(Cond::any()
.add(Cond::all()) // empty all() => TRUE
.add(Cond::any()) // empty any() => FALSE
)
.build(sea_query::MysqlQueryBuilder);

// old behavior
assert_eq!(statement, r#"SELECT `id` FROM `glyph`"#);

// new behavior
assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE (TRUE) OR (FALSE)"#
);

// a complex example
let (statement, values) = Query::select()
.column(Glyph::Id)
.from(Glyph::Table)
.cond_where(
Cond::all()
.add(Cond::all().not())
.add(Cond::any().not())
.not(),
)
.build(MysqlQueryBuilder);

assert_eq!(
statement,
r#"SELECT `id` FROM `glyph` WHERE NOT ((NOT TRUE) AND (NOT FALSE))"#
);

Breaking Changesโ€‹

  • [#535] MSRV is up to 1.62
# Make sure you're running SeaQuery with Rust 1.62+ ๐Ÿฆ€
$ rustup update
  • [#492] ColumnType::Array definition changed from Array(SeaRc<Box<ColumnType>>) to Array(SeaRc<ColumnType>)
  • [#475] Func::* now returns FunctionCall instead of SimpleExpr
  • [#475] Func::coalesce now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Into<SimpleExpr>
  • [#475] Removed Expr::arg and Expr::args - these functions are no longer needed
  • [#507] Moved all Postgres specific operators to PgBinOper
  • [#476] Expr methods used to accepts Into<Value> now accepts Into<SimpleExpr>
  • [#476] Expr::is_in, Expr::is_not_in now accepts Into<SimpleExpr> instead of Into<Value> and convert it to SimpleExpr::Tuple instead of SimpleExpr::Values
  • [#475] Expr::expr now accepts Into<SimpleExpr> instead of SimpleExpr
  • [#519] Moved Postgres specific Expr methods to new trait PgExpr
  • [#528] Expr::equals now accepts C: IntoColumnRef instead of T: IntoIden, C: IntoIden
use sea_query::{*, tests_cfg::*};

let query = Query::select()
.columns([Char::Character, Char::SizeW, Char::SizeH])
.from(Char::Table)
.and_where(
Expr::col((Char::Table, Char::FontId))
- .equals(Font::Table, Font::Id)
+ .equals((Font::Table, Font::Id))
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `character`, `size_w`, `size_h` FROM `character` WHERE `character`.`font_id` = `font`.`id`"#
);
  • [#525] Removed integer and date time column types' display length / precision option

API Additionsโ€‹

  • [#475] Added SelectStatement::from_function
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.column(ColumnRef::Asterisk)
.from_function(Func::random(), Alias::new("func"))
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT * FROM RAND() AS `func`"#
);
  • [#486] Added binary operators from the Postgres pg_trgm extension
use sea_query::extension::postgres::PgBinOper;

assert_eq!(
Query::select()
.expr(Expr::col(Font::Name).binary(PgBinOper::WordSimilarity, Expr::value("serif")))
.from(Font::Table)
.to_string(PostgresQueryBuilder),
r#"SELECT "name" <% 'serif' FROM "font""#
);
  • [#473] Added ILIKE and NOT ILIKE operators
  • [#510] Added the mul and div methods for SimpleExpr
  • [#513] Added the MATCH, -> and ->> operators for SQLite
use sea_query::extension::sqlite::SqliteBinOper;

assert_eq!(
Query::select()
.column(Char::Character)
.from(Char::Table)
.and_where(Expr::col(Char::Character).binary(SqliteBinOper::Match, Expr::val("test")))
.build(SqliteQueryBuilder),
(
r#"SELECT "character" FROM "character" WHERE "character" MATCH ?"#.to_owned(),
Values(vec!["test".into()])
)
);
  • [#497] Added the FULL OUTER JOIN
  • [#530] Added PgFunc::get_random_uuid
  • [#528] Added SimpleExpr::eq, SimpleExpr::ne, Expr::not_equals
  • [#529] Added PgFunc::starts_with
  • [#535] Added Expr::custom_keyword and SimpleExpr::not
use sea_query::*;

let query = Query::select()
.expr(Expr::custom_keyword(Alias::new("test")))
.to_owned();

assert_eq!(query.to_string(MysqlQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(PostgresQueryBuilder), r#"SELECT test"#);
assert_eq!(query.to_string(SqliteQueryBuilder), r#"SELECT test"#);
  • [#539] Added SimpleExpr::like, SimpleExpr::not_like and Expr::cast_as
  • [#532] Added support for NULLS NOT DISTINCT clause for Postgres
  • [#531] Added Expr::cust_with_expr and Expr::cust_with_exprs
use sea_query::{tests_cfg::*, *};

let query = Query::select()
.expr(Expr::cust_with_expr("data @? ($1::JSONPATH)", "hello"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT data @? ('hello'::JSONPATH)"#
);
  • [#538] Added support for converting &String to Value

Miscellaneous Enhancementsโ€‹

  • [#475] New struct FunctionCall which hold function and arguments
  • [#503] Support BigDecimal, IpNetwork and MacAddress for sea-query-postgres
  • [#511] Made value::with_array module public and therefore making NotU8 trait public
  • [#524] Drop the Sized requirement on implementers of SchemaBuilders

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 4 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release Seaography 0.3.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#93] We have upgraded a major dependency:

You might need to upgrade the corresponding dependency in your application as well.

Support Self Referencing Relationโ€‹

[#99] You can now query self referencing models and the inverse of it.

Self referencing relation should be added to the Relation enum, note that the belongs_to attribute must be belongs_to = "Entity".

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

#[derive(
Clone, Debug, PartialEq, DeriveEntityModel,
async_graphql::SimpleObject, seaography::macros::Filter,
)]
#[sea_orm(table_name = "staff")]
#[graphql(complex)]
#[graphql(name = "Staff")]
pub struct Model {
#[sea_orm(primary_key)]
pub staff_id: i32,
pub first_name: String,
pub last_name: String,
pub reports_to_id: Option<i32>,
}

#[derive(
Copy, Clone, Debug, EnumIter, DeriveRelation,
seaography::macros::RelationsCompact
)]
pub enum Relation {
#[sea_orm(
belongs_to = "Entity",
from = "Column::ReportsToId",
to = "Column::StaffId",
)]
SelfRef,
}

impl ActiveModelBehavior for ActiveModel {}

Then, you can query the related models in GraphQL.

{
staff {
nodes {
firstName
reportsToId
selfRefReverse {
staffId
firstName
}
selfRef {
staffId
firstName
}
}
}
}

The resulting JSON

{
"staff": {
"nodes": [
{
"firstName": "Mike",
"reportsToId": null,
"selfRefReverse": [
{
"staffId": 2,
"firstName": "Jon"
}
],
"selfRef": null
},
{
"firstName": "Jon",
"reportsToId": 1,
"selfRefReverse": null,
"selfRef": {
"staffId": 1,
"firstName": "Mike"
}
}
]
}
}

Web Framework Generatorโ€‹

[#74] You can generate seaography project with either Actix or Poem as the web server.

CLI Generator Optionโ€‹

Run seaography-cli to generate seaography code with Actix or Poem as the web framework.

# The command take three arguments, generating project with Poem web framework by default
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# Generating project with Actix web framework
seaography-cli -f actix <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Actixโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse};
use sea_orm::Database;
use seaography_example_project::*;
// ...

async fn graphql_playground() -> Result<HttpResponse> {
Ok(HttpResponse::Ok()
.content_type("text/html; charset=utf-8")
.body(
playground_source(GraphQLPlaygroundConfig::new("http://localhost:8000"))
))
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader {
db: database.clone(),
},
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = App::new()
.app_data(Data::new(schema.clone()))
.service(web::resource("/").guard(guard::Post()).to(index))
.service(web::resource("/").guard(guard::Get()).to(graphql_playground));

HttpServer::new(app)
.bind("127.0.0.1:8000")?
.run()
.await
}

Poemโ€‹

use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

[#84] Filtering, sorting and paginating related 1-to-many queries. Note that the pagination is work-in-progress, currently it is in memory pagination.

For example, find all inactive customers, include their address, and their payments with amount greater than 7 ordered by amount the second result. You can execute the query below at our GraphQL playground.

{
customer(
filters: { active: { eq: 0 } }
pagination: { cursor: { limit: 3, cursor: "Int[3]:271" } }
) {
nodes {
customerId
lastName
email
address {
address
}
payment(
filters: { amount: { gt: "7" } }
orderBy: { amount: ASC }
pagination: { pages: { limit: 1, page: 1 } }
) {
nodes {
paymentId
amount
}
pages
current
pageInfo {
hasPreviousPage
hasNextPage
}
}
}
pageInfo {
hasPreviousPage
hasNextPage
endCursor
}
}
}

Integration Examplesโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

Communityโ€‹

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

ยท 7 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.10.0!

Rust 1.65โ€‹

The long-anticipated Rust 1.65 has been released! Generic associated types (GATs) must be the hottest newly-stabilized feature.

How is GAT useful to SeaORM? Let's take a look at the following:

trait StreamTrait<'a>: Send + Sync {
type Stream: Stream<Item = Result<QueryResult, DbErr>> + Send;

fn stream(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream, DbErr>> + 'a + Send>>;
}

You can see that the Future has a lifetime 'a, but as a side effect the lifetime is tied to StreamTrait.

With GAT, the lifetime can be elided:

trait StreamTrait: Send + Sync {
type Stream<'a>: Stream<Item = Result<QueryResult, DbErr>> + Send
where
Self: 'a;

fn stream<'a>(
&'a self,
stmt: Statement,
) -> Pin<Box<dyn Future<Output = Result<Self::Stream<'a>, DbErr>> + 'a + Send>>;
}

What benefit does it bring in practice? Consider you have a function that accepts a generic ConnectionTrait and calls stream():

async fn processor<'a, C>(conn: &'a C) -> Result<...>
where C: ConnectionTrait + StreamTrait<'a> {...}

The fact that the lifetime of the connection is tied to the stream can create confusion to the compiler, most likely when you are making transactions:

async fn do_transaction<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + TransactionTrait
{
let txn = conn.begin().await?;
processor(&txn).await?;
txn.commit().await?;
}

But now, with the lifetime of the stream elided, it's much easier to work on streams inside transactions because the two lifetimes are now distinct and the stream's lifetime is implicit:

async fn processor<C>(conn: &C) -> Result<...>
where C: ConnectionTrait + StreamTrait {...}

Big thanks to @nappa85 for the contribution.


Below are some feature highlights ๐ŸŒŸ:

Support Array Data Types in Postgresโ€‹

[#1132] Support model field of type Vec<T>. (by @hf29h8sh321, @ikrivosheev, @tyt2y3, @billy1624)

You can define a vector of types that are already supported by SeaORM in the model.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "collection")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub integers: Vec<i32>,
pub integers_opt: Option<Vec<i32>>,
pub floats: Vec<f32>,
pub doubles: Vec<f64>,
pub strings: Vec<String>,
}

Keep in mind that you need to enable the postgres-array feature and this is a Postgres only feature.

sea-orm = { version = "0.10", features = ["postgres-array", ...] }

Better Error Typesโ€‹

[#750, #1002] Error types with parsable database specific error. (by @mohs8421, @tyt2y3)

let mud_cake = cake::ActiveModel {
id: Set(1),
name: Set("Moldy Cake".to_owned()),
price: Set(dec!(10.25)),
gluten_free: Set(false),
serial: Set(Uuid::new_v4()),
bakery_id: Set(None),
};

// Insert a new cake with its primary key (`id` column) set to 1.
let cake = mud_cake.save(db).await.expect("could not insert cake");

// Insert the same row again and it failed
// because primary key of each row should be unique.
let error: DbErr = cake
.into_active_model()
.insert(db)
.await
.expect_err("inserting should fail due to duplicate primary key");

match error {
DbErr::Exec(RuntimeErr::SqlxError(error)) => match error {
Error::Database(e) => {
// We check the error code thrown by the database (MySQL in this case),
// `23000` means `ER_DUP_KEY`: we have a duplicate key in the table.
assert_eq!(e.code().unwrap(), "23000");
}
_ => panic!("Unexpected sqlx-error kind"),
},
_ => panic!("Unexpected Error kind"),
}

Run Migration on Any Postgres Schemaโ€‹

[#1056] By default migration will be run on the public schema, you can now override it when running migration on the CLI or programmatically. (by @MattGson, @nahuakang, @billy1624)

For CLI, you can specify the target schema with -s / --database_schema option:

  • via sea-orm-cli: sea-orm-cli migrate -u postgres://root:root@localhost/database -s my_schema
  • via SeaORM migrator: cargo run -- -u postgres://root:root@localhost/database -s my_schema

You can also run the migration on the target schema programmatically:

let connect_options = ConnectOptions::new("postgres://root:root@localhost/database".into())
.set_schema_search_path("my_schema".into()) // Override the default schema
.to_owned();

let db = Database::connect(connect_options).await?

migration::Migrator::up(&db, None).await?;

Breaking Changesโ€‹

enum ColumnType {
// then
Enum(String, Vec<String>)

// now
Enum {
/// Name of enum
name: DynIden,
/// Variants of enum
variants: Vec<DynIden>,
}
...
}
  • A new method array_type was added to ValueType:
impl sea_orm::sea_query::ValueType for MyType {
fn array_type() -> sea_orm::sea_query::ArrayType {
sea_orm::sea_query::ArrayType::TypeName
}
...
}
  • ActiveEnum::name() changed return type to DynIden:
#[derive(Debug, Iden)]
#[iden = "category"]
pub struct CategoryEnum;

impl ActiveEnum for Category {
// then
fn name() -> String {
"category".to_owned()
}

// now
fn name() -> DynIden {
SeaRc::new(CategoryEnum)
}
...
}

SeaORM Enhancementsโ€‹

CLI Enhancementsโ€‹

Please check here for the complete changelog.

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Henrik Giesel
Jacob Trueb
Marcus Buffett
Unnamed Sponsor
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.11.x.

ยท 2 min read
SeaQL Team

Not long ago we opened a PR "Toggle stacked download graph #5010" resolving Convert download chart from stacked chart to regular chart #3876 for crates.io.

What's it all about?

Problemโ€‹

The download graph on crates.io used to be a stacked graph. With download count of older versions stack on top of newer versions. You might misinterpret the numbers. Consider this, at the first glance, it seems that version 0.9.2 has 1,500+ downloads on Nov 7. But in fact, it has only 237 downloads that day because the graph is showing the cumulative downloads.

crates.io Stacked Download Graph

This makes it hard to compare the download trend of different versions over time. Why this is important? You may ask. It's important to observe the adoption rate of newer version upon release. This paints a general picture if existing users are upgrading to newer version or not.

Solutionโ€‹

The idea is simple but effective: having a dropdown to toggle between stacked and unstacked download graph. With this, one can switch between both display mode, comparing the download trend of different version and observing the most download version in the past 90 days are straightforward and intuitive.

crates.io Unstacked Download Graph

Conclusionโ€‹

This is a great tool for us to gauge the adoption rate of our new releases and we highly encourage user upgrading to newer release that contains feature updates and bug fixes.

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.27.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradeโ€‹

[#356] We have upgraded a major dependency:

  • Upgrade sqlx to 0.6.1

You might need to upgrade the corresponding dependency in your application as well.

Drivers supportโ€‹

We have reworked the way drivers work in SeaQuery: priori to 0.27.0, users have to invoke the sea_query_driver_* macros. Now each driver sqlx, postgres & rusqlite has their own supporting crate, which integrates tightly with the corresponding libraries. Checkout our integration examples below for more details.

[#383] Deprecate sea-query-driver in favour of sea-query-binder

[#422] Rusqlite support is moved to sea-query-rusqlite

[#433] Postgres support is moved to sea-query-postgres

// before
sea_query::sea_query_driver_postgres!();
use sea_query_driver_postgres::{bind_query, bind_query_as};

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build(PostgresQueryBuilder);

let row = bind_query(sqlx::query(&sql), &values)
.fetch_one(&mut pool)
.await
.unwrap();

// now
use sea_query_binder::SqlxBinder;

let (sql, values) = Query::select()
.from(Character::Table)
.expr(Func::count(Expr::col(Character::Id)))
.build_sqlx(PostgresQueryBuilder);

let row = sqlx::query_with(&sql, values)
.fetch_one(&mut pool)
.await
.unwrap();

// You can now make use of SQLx's `query_as_with` nicely:
let rows = sqlx::query_as_with::<_, StructWithFromRow, _>(&sql, values)
.fetch_all(&mut pool)
.await
.unwrap();

Support sub-query operators: EXISTS, ALL, ANY, SOMEโ€‹

[#118] Added sub-query operators: EXISTS, ALL, ANY, SOME

let query = Query::select()
.column(Char::Id)
.from(Char::Table)
.and_where(
Expr::col(Char::Id)
.eq(
Expr::any(
Query::select().column(Char::Id).from(Char::Table).take()
)
)
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"SELECT `id` FROM `character` WHERE `id` = ANY(SELECT `id` FROM `character`)"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT "id" FROM "character" WHERE "id" = ANY(SELECT "id" FROM "character")"#
);

Support ON CONFLICT WHEREโ€‹

[#366] Added support to ON CONFLICT WHERE

let query = Query::insert()
.into_table(Glyph::Table)
.columns([Glyph::Aspect, Glyph::Image])
.values_panic(vec![
2.into(),
3.into(),
])
.on_conflict(
OnConflict::column(Glyph::Id)
.update_expr((Glyph::Image, Expr::val(1).add(2)))
.target_and_where(Expr::tbl(Glyph::Table, Glyph::Aspect).is_null())
.to_owned()
)
.to_owned();

assert_eq!(
query.to_string(MysqlQueryBuilder),
r#"INSERT INTO `glyph` (`aspect`, `image`) VALUES (2, 3) ON DUPLICATE KEY UPDATE `image` = 1 + 2"#
);
assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);
assert_eq!(
query.to_string(SqliteQueryBuilder),
r#"INSERT INTO "glyph" ("aspect", "image") VALUES (2, 3) ON CONFLICT ("id") WHERE "glyph"."aspect" IS NULL DO UPDATE SET "image" = 1 + 2"#
);

Changed cond_where chaining semanticsโ€‹

[#414] Changed cond_where chaining semantics

// Before: will extend current Condition
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.cond_where(Expr::col(Glyph::Id).eq(3))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 OR "id" = 2 OR "id" = 3"#
);
// Before: confusing, since it depends on the order of invocation:
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(3))
.cond_where(any![Expr::col(Glyph::Id).eq(1), Expr::col(Glyph::Id).eq(2)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 3 AND ("id" = 1 OR "id" = 2)"#
);
// Now: will always conjoin with `AND`
assert_eq!(
Query::select()
.cond_where(Expr::col(Glyph::Id).eq(1))
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE "id" = 1 AND ("id" = 2 OR "id" = 3)"#
);
// Now: so they are now equivalent
assert_eq!(
Query::select()
.cond_where(any![Expr::col(Glyph::Id).eq(2), Expr::col(Glyph::Id).eq(3)])
.cond_where(Expr::col(Glyph::Id).eq(1))
.to_owned()
.to_string(PostgresQueryBuilder),
r#"SELECT WHERE ("id" = 2 OR "id" = 3) AND "id" = 1"#
);

Added OnConflict::value and OnConflict::valuesโ€‹

[#451] Implementation From<T> for any Into<Value> into SimpleExpr

// Before: notice the tuple
OnConflict::column(Glyph::Id).update_expr((Glyph::Image, Expr::val(1).add(2)))
// After: it accepts `Value` as well as `SimpleExpr`
OnConflict::column(Glyph::Id).value(Glyph::Image, Expr::val(1).add(2))

Improvement to ColumnDef::defaultโ€‹

[#347] ColumnDef::default now accepts Into<SimpleExpr> instead Into<Value>

// Now we can write:
ColumnDef::new(Char::FontId)
.timestamp()
.default(Expr::current_timestamp())

Breaking Changesโ€‹

  • [#386] Changed in_tuples interface to accept IntoValueTuple
  • [#320] Removed deprecated methods
  • [#440] CURRENT_TIMESTAMP changed from being a function to keyword
  • [#375] Update SQLite boolean type from integer to boolean`
  • [#451] Deprecated OnConflict::update_value, OnConflict::update_values, OnConflict::update_expr, OnConflict::update_exprs
  • [#451] Deprecated InsertStatement::exprs, InsertStatement::exprs_panic
  • [#451] Deprecated UpdateStatement::col_expr, UpdateStatement::value_expr, UpdateStatement::exprs
  • [#451] UpdateStatement::value now accept Into<SimpleExpr> instead of Into<Value>
  • [#451] Expr::case, CaseStatement::case and CaseStatement::finally now accepts Into<SimpleExpr> instead of Into<Expr>
  • [#460] InsertStatement::values, UpdateStatement::values now accepts IntoIterator<Item = SimpleExpr> instead of IntoIterator<Item = Value>
  • [#409] Use native api from SQLx for SQLite to work with time
  • [#435] Changed type of ColumnType::Enum from (String, Vec<String>) to Enum { name: DynIden, variants: Vec<DynIden>}

Miscellaneous Enhancementsโ€‹

  • [#336] Added support one dimension Postgres array for SQLx
  • [#373] Support CROSS JOIN
  • [#457] Added support DROP COLUMN for SQLite
  • [#466] Added YEAR, BIT and VARBIT types
  • [#338] Handle Postgres schema name for schema statements
  • [#418] Added %, << and >> binary operators
  • [#329] Added RAND function
  • [#425] Implements Display for Value
  • [#427] Added INTERSECT and EXCEPT to UnionType
  • [#448] OrderedStatement::order_by_customs, OrderedStatement::order_by_columns, OverStatement::partition_by_customs, OverStatement::partition_by_columns now accepts IntoIterator<Item = T> instead of Vec<T>
  • [#452] TableAlterStatement::rename_column, TableAlterStatement::drop_column, ColumnDef::new, ColumnDef::new_with_type now accepts IntoIden instead of Iden
  • [#426] Cleanup IndexBuilder trait methods
  • [#436] Introduce SqlWriter trait
  • [#448] Remove unneeded vec! from examples

Bug Fixesโ€‹

  • [#449] distinct_on properly handles ColumnRef
  • [#461] Removed ON for DROP INDEX for SQLite
  • [#468] Change datetime string format to include microseconds
  • [#452] ALTER TABLE for PosgreSQL with UNIQUE constraint

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 6 min read
SeaQL Team

Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

The design and implementation of Seaography can be found on our release blog post and documentation.

Extending a SeaORM projectโ€‹

Since Seaography is built on top of SeaORM, you can easily build a GraphQL server from a SeaORM project.

Start by adding Seaography and GraphQL dependencies to your Cargo.toml.

Cargo.toml
[dependencies]
sea-orm = { version = "^0.9", features = [ ... ] }
+ seaography = { version = "^0.1", features = [ "with-decimal", "with-chrono" ] }
+ async-graphql = { version = "4.0.10", features = ["decimal", "chrono", "dataloader"] }
+ async-graphql-poem = { version = "4.0.10" }

Then, derive a few macros on the SeaORM entities.

src/entities/film_actor.rs
use sea_orm::entity::prelude::*;

#[derive(
Clone,
Debug,
PartialEq,
DeriveEntityModel,
+ async_graphql::SimpleObject,
+ seaography::macros::Filter,
)]
+ #[graphql(complex)]
+ #[graphql(name = "FilmActor")]
#[sea_orm(table_name = "film_actor")]
pub struct Model {
#[sea_orm(primary_key, auto_increment = false)]
pub actor_id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub film_id: i32,
pub last_update: DateTimeUtc,
}

#[derive(
Copy,
Clone,
Debug,
EnumIter,
DeriveRelation,
+ seaography::macros::RelationsCompact,
)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::film::Entity",
from = "Column::FilmId",
to = "super::film::Column::FilmId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Film,
#[sea_orm(
belongs_to = "super::actor::Entity",
from = "Column::ActorId",
to = "super::actor::Column::ActorId",
on_update = "Cascade",
on_delete = "NoAction"
)]
Actor,
}

We also need to define QueryRoot for the GraphQL server. This define the GraphQL schema.

src/query_root.rs
#[derive(Debug, seaography::macros::QueryRoot)]
#[seaography(entity = "crate::entities::actor")]
#[seaography(entity = "crate::entities::film")]
#[seaography(entity = "crate::entities::film_actor")]
pub struct QueryRoot;
src/lib.rs
use sea_orm::prelude::*;

pub mod entities;
pub mod query_root;

pub use query_root::QueryRoot;

pub struct OrmDataloader {
pub db: DatabaseConnection,
}

Finally, create an executable to drive the GraphQL server.

src/main.rs
use async_graphql::{
dataloader::DataLoader,
http::{playground_source, GraphQLPlaygroundConfig},
EmptyMutation, EmptySubscription, Schema,
};
use async_graphql_poem::GraphQL;
use poem::{handler, listener::TcpListener, web::Html, IntoResponse, Route, Server};
use sea_orm::Database;
use seaography_example_project::*;
// ...

#[handler]
async fn graphql_playground() -> impl IntoResponse {
Html(playground_source(GraphQLPlaygroundConfig::new("/")))
}

#[tokio::main]
async fn main() {
// ...

let database = Database::connect(db_url).await.unwrap();
let orm_dataloader: DataLoader<OrmDataloader> = DataLoader::new(
OrmDataloader { db: database.clone() },
tokio::spawn,
);

let schema = Schema::build(QueryRoot, EmptyMutation, EmptySubscription)
.data(database)
.data(orm_dataloader)
.finish();

let app = Route::new()
.at("/", get(graphql_playground)
.post(GraphQL::new(schema)));

Server::new(TcpListener::bind("0.0.0.0:8000"))
.run(app)
.await
.unwrap();
}

Generating a project from databaseโ€‹

If all you have is a database schema, good news! You can setup a GraphQL server without writing a single line of code.

Install seaography-cli, it helps you generate SeaORM entities along with a full Rust project based on a database schema.

cargo install seaography-cli

Run seaography-cli to generate code for the GraphQL server.

# The command take three arguments
seaography-cli <DATABASE_URL> <CRATE_NAME> <DESTINATION>

# MySQL
seaography-cli mysql://root:root@localhost/sakila seaography-mysql-example examples/mysql
# PostgreSQL
seaography-cli postgres://root:root@localhost/sakila seaography-postgres-example examples/postgres
# SQLite
seaography-cli sqlite://examples/sqlite/sakila.db seaography-sqlite-example examples/sqliteql

Checkout the example projectsโ€‹

We have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

Starting the GraphQL Serverโ€‹

Your GraphQL server is ready to launch! Go to the Rust project root then execute cargo run to spin it up.

$ cargo run

Playground: http://localhost:8000

Visit the GraphQL playground at http://localhost:8000

GraphQL Playground

Query Data via GraphQLโ€‹

Let say we want to get the first 3 films released on or after year 2006 sorted in ascending order of its title.

{
film(
pagination: { limit: 3, page: 0 }
filters: { releaseYear: { gte: "2006" } }
orderBy: { title: ASC }
) {
data {
filmId
title
description
releaseYear
filmActor {
actor {
actorId
firstName
lastName
}
}
}
pages
current
}
}

We got the following JSON result after running the GraphQL query.

{
"data": {
"film": {
"data": [
{
"filmId": 1,
"title": "ACADEMY DINOSAUR",
"description": "An Epic Drama of a Feminist And a Mad Scientist who must Battle a Teacher in The Canadian Rockies",
"releaseYear": "2006",
"filmActor": [
{
"actor": {
"actorId": 1,
"firstName": "PENELOPE",
"lastName": "GUINESS"
}
},
{
"actor": {
"actorId": 10,
"firstName": "CHRISTIAN",
"lastName": "GABLE"
}
},
// ...
]
},
{
"filmId": 2,
"title": "ACE GOLDFINGER",
"description": "A Astounding Epistle of a Database Administrator And a Explorer who must Find a Car in Ancient China",
"releaseYear": "2006",
"filmActor": [
// ...
]
},
// ...
],
"pages": 334,
"current": 0
}
}
}

Behind the scene, the following SQL were queried:

SELECT "film"."film_id",
"film"."title",
"film"."description",
"film"."release_year",
"film"."language_id",
"film"."original_language_id",
"film"."rental_duration",
"film"."rental_rate",
"film"."length",
"film"."replacement_cost",
"film"."rating",
"film"."special_features",
"film"."last_update"
FROM "film"
WHERE "film"."release_year" >= '2006'
ORDER BY "film"."title" ASC
LIMIT 3 OFFSET 0

SELECT "film_actor"."actor_id", "film_actor"."film_id", "film_actor"."last_update"
FROM "film_actor"
WHERE "film_actor"."film_id" IN (1, 3, 2)

SELECT "actor"."actor_id", "actor"."first_name", "actor"."last_name", "actor"."last_update"
FROM "actor"
WHERE "actor"."actor_id" IN (24, 162, 20, 160, 1, 188, 123, 30, 53, 40, 2, 64, 85, 198, 10, 19, 108, 90)

Under the hood, Seaography uses async_graphql::dataloader in querying nested objects to tackle the N+1 problem.

To learn more, checkout the Seaography Documentation.

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL

ยท 4 min read
SeaQL Team

What a fruitful Summer of Code! Today, we are excited to introduce Seaography to the SeaQL community. Seaography is a GraphQL framework for building GraphQL resolvers using SeaORM. It ships with a CLI tool that can generate ready-to-compile Rust projects from existing MySQL, Postgres and SQLite databases.

Motivationโ€‹

We observed that other ecosystems have similar tools such as PostGraphile and Hasura allowing users to query a database via GraphQL with minimal effort upfront. We decided to bring that seamless experience to the Rust ecosystem.

For existing SeaORM users, adding a GraphQL API is straight forward. Start by adding seaography and async-graphql dependencies to your crate. Then, deriving a few extra derive macros to the SeaORM entities. Finally, spin up a GraphQL server to serve queries!

If you are new to SeaORM, no worries, we have your back. You only need to provide a database connection, and seaography-cli will generate the SeaORM entities together with a complete Rust project!

Designโ€‹

We considered two approaches in our initial discussion: 1) blackbox query engine 2) code generator. The drawback with a blackbox query engine is it's difficult to customize or extend its behaviour, making it difficult to develop and operate in the long run. We opted the code generator approach, giving users full control and endless possibilities with the versatile async Rust ecosystem.

This project is separated into the following crates:

  • seaography: The facade crate; exporting macros, structures and helper functions to turn SeaORM entities into GraphQL nodes.

  • seaography-cli: The CLI tool; it generates SeaORM entities along with a full Rust project based on a user-provided database.

  • seaography-discoverer: A helper crate used by the CLI tool to discover the database schema and transform into a generic format.

  • seaography-generator: A helper crate used by the CLI tool to consume the database schema and generate a full Rust project.

  • seaography-derive: A set of procedural macros to derive types and trait implementations on SeaORM entities, turning them into GraphQL nodes.

Featuresโ€‹

  • Relational query (1-to-1, 1-to-N)
  • Pagination on query's root entity
  • Filter with operators (e.g. gt, lt, eq)
  • Order by any column

Getting Startedโ€‹

To quick start, we have the following examples for you, alongside with the SQL scripts to initialize the database.

All examples provide a web-based GraphQL playground when running, so you can inspect the GraphQL schema and make queries. We also hosted a demo GraphQL playground in case you can't wait to play with it.

For more documentation, visit www.sea-ql.org/Seaography.

What's Next?โ€‹

This project passed the first milestone shipping the essential features, but it still has a long way to go. The next milestone would be:

  • Query enhancements
    • Filter related queries
    • Filter based on related queries properties
    • Paginate related queries
    • Order by related queries
  • Cursor based pagination
  • Single entity query
  • Mutations
    • Insert single entity
    • Insert batch entities
    • Update single entity
    • Update batch entities using filter
    • Delete single entity
    • Delete batch entities

Conclusionโ€‹

Seaography is an ergonomic library that turns SeaORM entities into GraphQL nodes. It provides a set of utilities and combined with a code generator makes GraphQL API building a breeze.

However, Seaography is still a new-born. Like all other open-source projects developed by passionate Rust developers, you can contribute to it if you also find the concept interesting. With its addition to the SeaQL ecosystem, we are one step closer to the vision of Rust being the best tool for data engineering.

Peopleโ€‹

Seaography is created by:

Panagiotis Karatakis
Summer of Code Contributor; developer of Seaography
Chris Tsang
Summer of Code Mentor; lead developer of SeaQL
Billy Chan
Summer of Code Mentor; core member of SeaQL

ยท 6 min read
SeaQL Team

We are celebrating the milestone of reaching 3,000 GitHub stars across all SeaQL repositories!

This wouldn't have happened without your support and contribution, so we want to thank the community for being with us along the way.

The Journeyโ€‹

SeaQL.org was founded back in 2020. We devoted ourselves into developing open source libraries that help Rust developers to build data intensive applications. In the past two years, we published and maintained four open source libraries: SeaQuery, SeaSchema, SeaORM and StarfishQL. Each library is designed to fill a niche in the Rust ecosystem, and they are made to play well with other Rust libraries.

2020โ€‹

  • Oct 2020: SeaQL founded
  • Dec 2020: SeaQuery first released

2021โ€‹

  • Apr 2021: SeaSchema first released
  • Aug 2021: SeaORM first released
  • Nov 2021: SeaORM reached 0.4.0
  • Dec 2021: SeaQuery reached 0.20.0
  • Dec 2021: SeaSchema reached 0.4.0

2022โ€‹

  • Apr 2022: SeaQL selected as a Google Summer of Code 2022 mentor organization
  • Apr 2022: StarfishQL first released
  • Jul 2022: SeaQuery reached 0.26.2
  • Jul 2022: SeaSchema reached 0.9.3
  • Jul 2022: SeaORM reached 0.9.1
  • Aug 2022: SeaQL reached 3,000+ GitHub stars

Where're We Now?โ€‹

We're pleased by the adoption by the Rust community. We couldn't make it this far without your feedback and contributions.

4 ๐Ÿ“ฆ
Open source projects
5 ๐Ÿฌ
Startups using SeaQL
1,972 ๐ŸŽˆ
Dependent projects
131 ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘งโ€๐Ÿ‘ฆ
Contributors
1,061 โœ…
Merged PRs & resolved issues
3,158 โญ
GitHub stars
432 ๐Ÿ—ฃ๏ธ
Discord members
87,937 โŒจ๏ธ
Lines of Rust
667,769 ๐Ÿ’ฟ
Downloads on crates.io

* as of Aug 12

Core Membersโ€‹

Our team has grown from two people initially into four. We always welcome passionate engineers to join us!

Chris Tsang
Founder. Led the initial development and maintaining the projects.
Billy Chan
Founding member. Contributed many features and bug fixes. Keeps the community alive.
Ivan Krivosheev
Joined in 2022. Contributed many features and bug fixes, most notably to SeaQuery.
Sanford Pun
Developed StarfishQL and wrote SeaORM's tutorial.

Special Thanksโ€‹

Marco Napetti
Contributed transaction, streaming and tracing API to SeaORM.
nitnelave
Contributed binder crate and other improvements to SeaQuery.
Sam Samai
Developed SeaORM's test suite and demo schema.
Daniel Lyne
Developed SeaSchema's Postgres implementation.
Charles Chege
Developed SeaSchema's SQLite implementation.

Sponsorsโ€‹

If you are feeling generous, a small donation will be greatly appreciated.

A big shout out to our sponsors ๐Ÿ˜‡:

ร‰mile Fugulin
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
Unnamed Sponsor

Contributorsโ€‹

Many features and enhancements are actually proposed and implemented by the community. We want to take this chance to thank all our contributors!

What's Next?โ€‹

We have two ongoing Summer of Code 2022 projects to enrich the SeaQL ecosystem, planning to be released later this year. In the meantime, we're focusing on improving existing SeaQL libraries until reaching version 1.0, we'd love to hear comments and feedback from the community.

If you like what we do, consider starring, commenting, sharing, contributing and together building for Rust's future!

ยท 3 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaQuery 0.26.0! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#356] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

VALUES listsโ€‹

[#351] Add support for VALUES lists

// SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x"
let query = SelectStatement::new()
.expr(Expr::asterisk())
.from_values(vec![(1i32, "hello"), (2, "world")], Alias::new("x"))
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT * FROM (VALUES (1, 'hello'), (2, 'world')) AS "x""#
);

Introduce sea-query-binderโ€‹

[#273] Native support SQLx without marcos

use sea_query_binder::SqlxBinder;

// Create SeaQuery query with prepare SQLx
let (sql, values) = Query::select()
.columns([
Character::Id,
Character::Uuid,
Character::Character,
Character::FontSize,
Character::Meta,
Character::Decimal,
Character::BigDecimal,
Character::Created,
Character::Inet,
Character::MacAddress,
])
.from(Character::Table)
.order_by(Character::Id, Order::Desc)
.build_sqlx(PostgresQueryBuilder);

// Execute query
let rows = sqlx::query_as_with::<_, CharacterStructChrono, _>(&sql, values)
.fetch_all(&mut pool)
.await?;

// Print rows
for row in rows.iter() {
println!("{:?}", row);
}

CASE WHEN statement supportโ€‹

[#304] Add support for CASE WHEN statement

let query = Query::select()
.expr_as(
CaseStatement::new()
.case(Expr::tbl(Glyph::Table, Glyph::Aspect).is_in(vec![2, 4]), Expr::val(true))
.finally(Expr::val(false)),
Alias::new("is_even")
)
.from(Glyph::Table)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT (CASE WHEN ("glyph"."aspect" IN (2, 4)) THEN TRUE ELSE FALSE END) AS "is_even" FROM "glyph""#
);

Add support for Ip(4,6)Network and MacAddressโ€‹

[#309] Add support for Network types in PostgreSQL backend

Introduce sea-query-attrโ€‹

[#296] Proc-macro for deriving Iden enum from struct

use sea_query::gen_type_def;

#[gen_type_def]
pub struct Hello {
pub name: String
}

println!("{:?}", HelloTypeDef::Name);

Add ability to alter foreign keysโ€‹

[#299] Add support for ALTER foreign Keys

let foreign_key_char = TableForeignKey::new()
.name("FK_character_glyph")
.from_tbl(Char::Table)
.from_col(Char::FontId)
.from_col(Char::Id)
.to_tbl(Glyph::Table)
.to_col(Char::FontId)
.to_col(Char::Id)
.to_owned();

let table = Table::alter()
.table(Character::Table)
.add_foreign_key(&foreign_key_char)
.to_owned();

assert_eq!(
table.to_string(PostgresQueryBuilder),
vec![
r#"ALTER TABLE "character""#,
r#"ADD CONSTRAINT "FK_character_glyph""#,
r#"FOREIGN KEY ("font_id", "id") REFERENCES "glyph" ("font_id", "id")"#,
r#"ON DELETE CASCADE ON UPDATE CASCADE,"#,
]
.join(" ")
);

Select DISTINCT ONโ€‹

[#250]

let query = Query::select()
.from(Char::Table)
.distinct_on(vec![Char::Character])
.column(Char::Character)
.column(Char::SizeW)
.column(Char::SizeH)
.to_owned();

assert_eq!(
query.to_string(PostgresQueryBuilder),
r#"SELECT DISTINCT ON ("character") "character", "size_w", "size_h" FROM "character""#
);

Miscellaneous Enhancementsโ€‹

  • [#353] Support LIKE ... ESCAPE ... expression
  • [#306] Move escape and unescape string to backend
  • [#365] Add method to make a column nullable
  • [#348] Add is & is_not to Expr
  • [#349] Add CURRENT_TIMESTAMP function
  • [#345] Add in_tuple method to Expr
  • [#266] Insert Default
  • [#324] Make sea-query-driver an optional dependency
  • [#334] Add ABS function
  • [#332] Support IF NOT EXISTS when create index
  • [#314] Support different blob types in MySQL
  • [#331] Add VarBinary column type
  • [#335] RETURNING expression supporting SimpleExpr

Integration Examplesโ€‹

SeaQuery plays well with the other crates in the rust ecosystem.

Communityโ€‹

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

ยท 5 min read
Chris Tsang

It's hard to pin down the exact date, but I think SeaQL.org was setup in July 2020, a little over a year ago. Over the course of the year, SeaORM went from 0.1 to 0.9 and the number of users kept growing. I would like to outline our engineering process in this blog post, and perhaps it can serve as a reference or guidance to prospective contributors and the future maintainer of this project.

In the open source world, the Benevolent Dictator for Life (BDL) model underpins a number of successful open source projects. That's not me! As a maintainer, I believe in an open, bottom-up, iterative and progressive approach. Let me explain each of these words and what they mean to me.

Openโ€‹

Open as in source availability, but also engineering. We always welcome new contributors! We'd openly discuss ideas and designs. I would often explain why a decision was made in the first place for various things. The project is structured not as a monorepo, but several interdependent repos. This reduces the friction for new contributors, because they can have a smaller field of vision to focus on solving one particular problem at hand.

Bottom-upโ€‹

We rely on users to file feature requests, bug reports and of course pull requests to drive the project forward. The great thing is, for every feature / bug fix, there is a use case for it and a confirmation from a real user that it works and is reasonable. As a maintainer, I could not have first hand experience for all features and so could not understand some of the pain points.

Iterativeโ€‹

Open source software is imperfect, impermanent and incomplete. While I do have a grand vision in mind, we do not try rushing it all the way in one charge, nor keeping a project secret until it is 'complete'. Good old 'release early, release often' - we would release an initial working version of a tool, gather user feedback and improve upon it, often reimplementing a few things and break a few others - which brings us to the next point.

Progressiveโ€‹

Favour progression. Always look forward and leave legacy behind. It does not mean that we would arbitrary break things, but when a decision is made, we'd always imagine how the software should be without historic context. We'd provide migrate paths and encourage users to move forward with us. After all, Rust is a young and evolving language! You may or may not know that async was just stabilized in 2020.

Enough said for the philosophy, let's now talk about the actual engineering process.

1. Idea & Designโ€‹

We first have some vague idea on what problem we want to tackle. As we put in more details to the use case, we can define the problem and brainstorm solutions. Then we look for workable ways to implement that in Rust.

2. Implementationโ€‹

An initial proof of concept is appreciated. We iterate on the implementation to reduce the impact and improve the maintainability.

3. Testingโ€‹

We rely on automated tests. Every feature should come with corresponding tests, and a release is good if and only if all tests are green. Which means for features not covered by our test suite, it is an uncertainty to when we would break them. So if certain undocumented feature is important to you, we encourage you to add that to our test suite.

4. Documentationโ€‹

Coding is not complete without documentation. Rust doc tests kill two birds with one stone and so is greatly appreciated. For SeaORM we have separate documentation repository and tutorial repository. It takes a lot of effort to maintain those to be up to date, and right now it's mostly done by our core contributors.

5. Releaseโ€‹

We run on a release train model, although the frequency varies. The ethos is to have small number breaking changes often. At one point, SeaQuery has a new release every week. SeaORM runs on monthly, although it more or less relaxes to bimonthly now. At any time, we maintain two branches, the latest release and master. PRs are always merged into master, and if it is non-breaking (and worthy) I would backport it to the release branch and make a minor release. At the end, I want to maintain momentum and move forward together with the community. Users can have a rough expectation on when merges will be released. And there are just lots of change we cannot avoid a breaking release as of the current state of the Rust ecosystem. Users are advised to upgrade regularly, and we ship along many small improvements to encourage that.

Conclusionโ€‹

Open source software is a collaborative effort and thank you all who participated! Also a big thanks to SeaQL's core contributors who made wonders. If you have not already, I invite you to star all our repositories. If you want to support us materially, a small donation would make a big difference. SeaQL the organization is still in its infancy, and your support is vital to SeaQL's longevity and the prospect of the Rust community.

ยท 11 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.9.0 today! Here are some feature highlights ๐ŸŒŸ:

Dependency Upgradesโ€‹

[#834] We have upgraded a few major dependencies:

Note that you might need to upgrade the corresponding dependency on your application as well.

Proposed by:
Rob Gilson
boraarslan
Contributed by:
Billy Chan

Cursor Paginationโ€‹

[#822] Paginate models based on column(s) such as the primary key.

// Create a cursor that order by `cake`.`id`
let mut cursor = cake::Entity::find().cursor_by(cake::Column::Id);

// Filter paginated result by `cake`.`id` > 1 AND `cake`.`id` < 100
cursor.after(1).before(100);

// Get first 10 rows (order by `cake`.`id` ASC)
let rows: Vec<cake::Model> = cursor.first(10).all(db).await?;

// Get last 10 rows (order by `cake`.`id` DESC but rows are returned in ascending order)
let rows: Vec<cake::Model> = cursor.last(10).all(db).await?;
Proposed by:
Lucas Berezy
Contributed by:
ร‰mile Fugulin
Billy Chan

Insert On Conflictโ€‹

[#791] Insert an active model with on conflict behaviour.

let orange = cake::ActiveModel {
id: ActiveValue::set(2),
name: ActiveValue::set("Orange".to_owned()),
};

// On conflict do nothing:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO NOTHING
cake::Entity::insert(orange.clone())
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.do_nothing()
.to_owned()
)
.exec(db)
.await?;

// On conflict do update:
// - INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("name") DO UPDATE SET "name" = "excluded"."name"
cake::Entity::insert(orange)
.on_conflict(
sea_query::OnConflict::column(cake::Column::Name)
.update_column(cake::Column::Name)
.to_owned()
)
.exec(db)
.await?;
Proposed by:
baoyachi. Aka Rust Hairy crabs
Contributed by:
liberwang1013

Join Table with Custom Conditions and Table Aliasโ€‹

[#793, #852] Click Custom Join Conditions and Custom Joins to learn more.

assert_eq!(
cake::Entity::find()
.column_as(
Expr::tbl(Alias::new("fruit_alias"), fruit::Column::Name).into_simple_expr(),
"fruit_name"
)
.join_as(
JoinType::LeftJoin,
cake::Relation::Fruit
.def()
.on_condition(|_left, right| {
Expr::tbl(right, fruit::Column::Name)
.like("%tropical%")
.into_condition()
}),
Alias::new("fruit_alias")
)
.build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake`.`id`, `cake`.`name`, `fruit_alias`.`name` AS `fruit_name` FROM `cake`",
"LEFT JOIN `fruit` AS `fruit_alias` ON `cake`.`id` = `fruit_alias`.`cake_id` AND `fruit_alias`.`name` LIKE '%tropical%'",
]
.join(" ")
);
Proposed by:
Chris Tsang
Tuetuopay
Loรฏc
Contributed by:
Billy Chan
Matt
liberwang1013

(de)serialize Custom JSON Typeโ€‹

[#794] JSON stored in the database could be deserialized into custom struct in Rust.

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "json_struct")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
// JSON column defined in `serde_json::Value`
pub json: Json,
// JSON column defined in custom struct
pub json_value: KeyValue,
pub json_value_opt: Option<KeyValue>,
}

// The custom struct must derive `FromJsonQueryResult`, `Serialize` and `Deserialize`
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromJsonQueryResult)]
pub struct KeyValue {
pub id: i32,
pub name: String,
pub price: f32,
pub notes: Option<String>,
}
Proposed by:
Mara Schulke
Chris Tsang
Contributed by:
Billy Chan

Derived Migration Nameโ€‹

[#736] Introduce DeriveMigrationName procedural macros to infer migration name from the file name.

use sea_orm_migration::prelude::*;

// Used to be...
pub struct Migration;

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

// Now... derive `DeriveMigrationName`,
// no longer have to specify the migration name explicitly
#[derive(DeriveMigrationName)]
pub struct Migration;

#[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
}
}
Proposed by:
Chris Tsang
Contributed by:
smonv
Lukas Potthast
Billy Chan

SeaORM CLI Improvementsโ€‹

  • [#735] Improve logging of generate entity command
  • [#588] Generate enum with numeric like variants
  • [#755] Allow old pending migration to be applied
  • [#837] Skip generating entity for ignored tables
  • [#724] Generate code for time crate
  • [#850] Add various blob column types
  • [#422] Generate entity files with Postgres's schema name
  • [#851] Skip checking connection string for credentials
Proposed & Contributed by:
ttys3
kyoto7250
yb3616
ร‰mile Fugulin
Bastian
Nahua
Mike
Frank Horvath
Maikel Wever

Miscellaneous Enhancementsโ€‹

  • [#800] Added sqlx_logging_level to ConnectOptions
  • [#768] Added num_items_and_pages to Paginator
  • [#849] Added TryFromU64 for time
  • [#853] Include column name in TryGetError::Null
  • [#778] Refactor stream metrics
Proposed & Contributed by:
SandaruKasa
Eric
ร‰mile Fugulin
Renato Dinhani
kyoto7250
Marco Napetti

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. We maintain an array of example projects for building REST, GraphQL and gRPC services. More examples wanted!

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
Dean Sheather
Shane Sveller
Sakti Dwi Cahyono
Unnamed Sponsor
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.10.x.

ยท 4 min read
SeaQL Team

We are thrilled to announce that we will bring in four contributors this summer! Two of them are sponsored by Google while two of them are sponsored by SeaQL.

A GraphQL Framework on Top of SeaORMโ€‹

Panagiotis Karatakis

I'm Panagiotis, I live in Athens Greece and currently I pursue my second bachelors on economic sciences. My first bachelors was on computer science and I've a great passion on studying and implementing enterprise software solutions. I know Rust the last year and I used it almost daily for a small startup project that me and my friends build for a startup competition.

I'll be working on creating a CLI tool that will explore a database schema and then generate a ready to build async-graphql API. The tool will allow quick integration with the SeaQL and Rust ecosystems as well as GraphQL. To be more specific, for database exploring I'll use sea-schema and sea-orm-codegen for entity generation, my job is to glue those together with async-graphql library. You can read more here.

SQL Interpreter for Mock Testingโ€‹

Samyak Sarnayak

I'm Samyak Sarnayak, a final year Computer Science student from Bangalore, India. I started learning Rust around 6-7 months ago and it feels like I have found the perfect language for me :D. It does not have a runtime, has a great type system, really good compiler errors, good tooling, some functional programming patterns and metaprogramming. You can find more about me on my GitHub profile.

I'll be working on a new SQL interpreter for mock testing. This will be built specifically for testing and so the emphasis will be on correctness - it can be slow but the operations must always be correct. I'm hoping to build a working version of this and integrate it into the existing tests of SeaORM. Here is the discussion for this project.

Support TiDB in the SeaQL Ecosystemโ€‹

Edit: This project was canceled.

Query Linter for SeaORMโ€‹

Edit: This project was canceled.

Mentorsโ€‹

Chris Tsang

I am a strong believer in open source. I started my GitHub journey 10 years ago, when I published my first programming library. I had been looking for a programming language with speed, ergonomic and expressiveness. Until I found Rust.

Seeing a niche and demand for data engineering tools in the Rust ecosystem, I founded SeaQL in 2020 and have been leading the development and maintaining the libraries since then.


Billy Chan

Hey, this is Billy from Hong Kong. I've been using open-source libraries ever since I started coding but it's until 2020, I dedicated myself to be a Rust open-source developer.

I was also a full-stack developer specialized in formulating requirement specifications for user interfaces and database structures, implementing and testing both frontend and backend from ground up, finally releasing the MVP for production and maintaining it for years to come.

I enjoy working with Rustaceans across the globe, building a better and sustainable ecosystem for Rust community. If you like what we do, consider starring, commenting, sharing and contributing, it would be much appreciated.


Sanford Pun

I'm Sanford, an enthusiastic software engineer who enjoys problem-solving! I've worked on Rust for a couple of years now. During my early days with Rust, I focused more on the field of graphics/image processing, where I fell in love with what the language has to offer! This year, I've been exploring data engineering in the StarfishQL project.

A toast to the endless potential of Rust!

Communityโ€‹

If you are interested in the projects and want to share your thoughts, please star and watch the SeaQL/summer-of-code repository on GitHub and join us on our Discord server!

ยท 5 min read
SeaQL Team

๐ŸŽ‰ We are pleased to release SeaORM 0.8.0 today! Here are some feature highlights ๐ŸŒŸ:

Migration Utilities Moved to sea-orm-migration crateโ€‹

[#666] Utilities of SeaORM migration have been moved from sea-schema to sea-orm-migration crate. Users are advised to upgrade from older versions with the following steps:

  1. Bump sea-orm version to 0.8.0.

  2. Replace sea-schema dependency with sea-orm-migration in your migration crate.

    migration/Cargo.toml
    [dependencies]
    - sea-schema = { version = "^0.7.0", ... }
    + sea-orm-migration = { version = "^0.8.0" }
  3. Find and replace use sea_schema::migration:: with use sea_orm_migration:: in your migration crate.

    - use sea_schema::migration::prelude::*;
    + use sea_orm_migration::prelude::*;

    - use sea_schema::migration::*;
    + use sea_orm_migration::*;
Designed by:

Chris Tsang
Contributed by:

Billy Chan

Generating New Migrationโ€‹

[#656] You can create a new migration with the migrate generate subcommand. This simplifies the migration process, as new migrations no longer need to be added manually.

# A migration file `MIGRATION_DIR/src/mYYYYMMDD_HHMMSS_create_product_table.rs` will be created.
# And, the migration file will be imported and included in the migrator located at `MIGRATION_DIR/src/lib.rs`.
sea-orm-cli migrate generate create_product_table
Proposed & Contributed by:

Viktor Bahr

Inserting One with Defaultโ€‹

[#589] Insert a row populate with default values. Note that the target table should have default values defined for all of its columns.

let pear = fruit::ActiveModel {
..Default::default() // all attributes are `NotSet`
};

// The SQL statement:
// - MySQL: INSERT INTO `fruit` VALUES ()
// - SQLite: INSERT INTO "fruit" DEFAULT VALUES
// - PostgreSQL: INSERT INTO "fruit" VALUES (DEFAULT) RETURNING "id", "name", "cake_id"
let pear: fruit::Model = pear.insert(db).await?;
Proposed by:

Crypto-Virus
Contributed by:

Billy Chan

Checking if an ActiveModel is changedโ€‹

[#683] You can check whether any field in an ActiveModel is Set with the help of the is_changed method.

let mut fruit: fruit::ActiveModel = Default::default();
assert!(!fruit.is_changed());

fruit.set(fruit::Column::Name, "apple".into());
assert!(fruit.is_changed());
Proposed by:

Karol Fuksiewicz
Contributed by:

Kirawi

Minor Improvementsโ€‹

  • [#670] Add max_connections option to sea-orm-cli generate entity subcommand
  • [#677] Derive Eq and Clone for DbErr
Proposed & Contributed by:

benluelo

Sebastien Guillemot

Integration Examplesโ€‹

SeaORM plays well with the other crates in the async ecosystem. It can be integrated easily with common RESTful frameworks and also gRPC frameworks; check out our new Tonic example to see how it works. More examples wanted!

Who's using SeaORM?โ€‹

The following products are powered by SeaORM:



A lightweight web security auditing toolkit

The enterprise ready webhooks service

A personal search engine

SeaORM is the foundation of StarfishQL, an experimental graph database and query engine.

For more projects, see Built with SeaORM.

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.9.x.

GSoC 2022โ€‹

We are super excited to be selected as a Google Summer of Code 2022 mentor organization. The application is now closed, but the program is about to start! If you have thoughts over how we are going to implement the project ideas, feel free to participate in the discussion.

ยท 2 min read
Chris Tsang

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.

ยท 8 min read
SeaQL Team

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 thatserde 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.ย a,ย a-,ย a0,ย  ...,ย zyx_test,ย zz-buffer,ย z_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.ย a,ย a-,ย a-bot,ย  ...,ย zzp-tools,ย zzz,ย z_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.

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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!

ยท 2 min read
SeaQL Team

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

ยท 5 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 4 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 2 min read
SeaQL Team

๐ŸŽ‰ 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.

ยท 5 min read
Chris Tsang

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

ยท One min read
Chris Tsang

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.

ยท One min read
Chris Tsang

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.

ยท One min read
Chris Tsang

One year ago, when we were writing data processing algorithms in Rust, we needed an async library to interface with a database. Back then, there weren't many choices. So we have to write our own.

December last year, we released SeaQuery, and received welcoming responses from the community. We decided to push the project further and develop a full blown async ORM.

It has been a bumpy ride, as designing an async ORM requires working within and sometimes around Rust's unique type system. After several iterations of experimentation, I think we've attained a balance between static & dynamic and compile-time & run-time that it offers benefits of the Rust language while still be familiar and easy-to-work-with for those who come from other languages.

SeaORM is tentative to be released in Sep 2021 and stabilize in May 2022. We hope that SeaORM will become a go-to choice for working with databases in Rust and that the Rust language will be adopted by more organizations in building applications.

If you are intrigued like I do, please stay in touch and join the community.

Share your thoughts here.

- + \ No newline at end of file