Introduction

Sea-ORM is an amazing ORM that aims to be a write code once and run on any popular Relational Database with current support for MySQL, PostgreSQL, MariaDB and SQLite. In this tutorial, SeaORM with be used with async-std as the async runtime, rustls for database TLS connections and sqlx-mysql for the MySQL database backend.

Installation of dependencies and tools

  1. Install SeaORM-Cli that will help in reading a database schema and generating the relevant Entity, Model and Relation of every table in our selected database (schema).

    $ cargo install sea-orm-cli
    
  2. Create a new Rust Cargo project

    $ cargo new SimpleCrud --name simple-crud
    
  3. Switch to the new cargo project

    $ cd simple-crud
    
  4. Add SeaORM as a dependency in Cargo.toml file

    If you have cargo edit installed, run

    $ cargo add sea-orm --no-default-features --features "runtime-async-std-rustls sqlx-mysql macros" 
    

    or if you don't have cargo edit installed, you can install it by running

    $ cargo install cargo-edit
    
  5. Add the async runtime

    $ cargo add anyhow
    
    $ cargo add async-std --features attributes
    

    You can also add them manually in the Cargo.toml file

    sea-orm = { version = "0.5", features = [ "runtime-async-std-rustls", "sqlx-mysql", "macros" ], default-features = false}
    anyhow = "1"
    async-std = "1"
    
  6. Make sure that your database server is running, then login and create a database called fruit_markets.

    CREATE DATABASE fruit_markets;
    
  7. Create a new user in the database called webmaster and with a password master_char

    # Step1: Create a new user
    CREATE USER 'webmaster'@'localhost' IDENTIFIED BY 'master_char';
    
    # Step 2: Allow the user to have Read, Write access to all tables in database `fruit_markets`
    GRANT ALL PRIVILEGES ON fruit_markets . * TO 'webmaster'@'localhost';
    
    # Step 3: Enable the above settings
    FLUSH PRIVILEGES;
    
    # Step 4: Logout of the database
    exit
    

We are all set to perform CRUD operations from the MySQL database side.

Symbols Used

To show added or removed code from files, we will use comments or

+ to show added code

- to show removed code

... is used to show only part of the existing code instead of rewriting already existing code in the examples.

$ shows an operation is done on the console/shell

This will make it easier to visualize changes to a file

In the next chapter, we will create simple CRUD operations.

Simple CRUD Operations

In this section, we will perform Create, Read, Update and Delete operations on the fruits table in the fruit_markets database. Each sub-section will focus on one database operation.

Creating the database configuration

Create a .env file that will hold the database configuration and open it in your favourite code editor. Then add the following configuration in the format DATABASE_URL=database://username:password@localhost/database_name

File: ./SimpleCRUD/.env

# Add this line to the .env file
DATABASE_URL=mysql://webmaster:master_char@localhost/fruit_markets

Create Operation

SeaORM abstracts database opertaions though the sea_orm::Database::connect() method which yields a DatabaseConnection. We will use this DatabaseConnection to execute database operations. Let's create the fruits table using the database connection.

Our goal is to do the SQL CREATE TABLE operation:

# Create a fruits table
CREATE TABLE fruits(
    fruit_id INT NOT NULL AUTO_INCREMENT,
    name VARCHAR(255) NOT NULL,
    datetime_utc TIMESTAMP NOT NULL,
    unit_price INT NOT NULL,
    sku VARCHAR(255) NOT NULL,
    PRIMARY KEY (fruit_id)
);

The fruits table is a record of all the fruits available, their id (fruit_id), name (name), the timestamp when the row was entered (datetime_utc) in the UTC timezone, the price per kilogram of fruit (unit_price) and the stock tracking alphanumeric code (sku) commonly known as Stock Keeping Unit.


Add a module fruits_table inside the src folder and add mod.rs, prelude.rs and fruit.rs files as its children.

FILE: SimpleCRUD/src/create_fruits_table.rs

|-- SimpleCRUD/
    |-- Cargo.toml
    |-- Cargo.lock
    |-- src/
        |-- main.rs
+    	|-- fruits_table/ #Code to create table fruits goes here
+			|-- fruits.rs
+			|-- mod.rs
+			|-- prelude.rs

Then, import this module

FILE: SimpleCRUD/src/main.rs

+ mod fruits_table;
+ use fruits_table::prelude::Fruits;

 // Import the needed modules for table creation
+ use sea_orm::{ConnectionTrait, Database, Schema};
// Handle errors using the `https://crates.io/crates/anyhow` crate
+ use anyhow::Result;

// Convert this main function into async function
+ #[async_std::main]
+ async fn main() -> Result<()>{
- fn main {
+
+    
+    Ok(())
+ }

The #[async_std::main] attribute is used to convert our main function fn main() {} into an async function async fn main() {} in order to use await inside main using async-std as the library.

Inside the fruit.rs add:

FILE: SimpleCRUD/src/fruits_table/fruit.rs

use sea_orm::entity::prelude::*;
    
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "fruits")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub fruit_id: i32,
    #[sea_orm(unique)]
    pub name: String,
    pub datetime_utc: DateTime,
    pub unit_price: i32,
    pub sku: String,
}

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

impl RelationTrait for Relation {
    fn def(&self) -> RelationDef {
        panic!("No RelationDef")
    }
}

impl ActiveModelBehavior for ActiveModel {}

The #[derive(... , DeriveEntityModel)] proc macro is used to automatically derive the code for Entity, Model and ActiveModel . For this to work, the struct MUST be called Model.

The enum Relation MUST also be created, currently, it has empty fields but if the table had a relationship with another table, this is where it would be illustrated. The #[derive(... , EnumIter)] is required on a Relation to ensure the type implements and exposes a Rust Iterator.

A Relation MUST implement a the Relation trait and the method def() of the trait. Currently, there is no relation so the def() method returns a panic!("No RelationDef") if we try to do operations like joins with other tables.

Lastly, we implement ActiveModelBehavior for the ActiveModel. The ActiveModel is autogenerated by SeaORM codegen when we derived #[derive(... , DeriveEntityModel)] from Model struct.

The #[sea_orm(primary_key)] is used to set the primary key and can be called using the ..Default::default() when instantiating a model.

#[sea_orm(unique)] derive macro is used on the name field to ensure that two rows are not entered with the same name. This corresponds to SQL UNIQUE constraint.

To set the field of a Model to a default of NULL , ensure the field is set to an Option<T> , for example, to set sku field of Model to SQL default of NULL:

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "fruits")]
pub struct Model {
    // -- shippet --
+   pub sku: Option<String>,
-   pub sku: String,
}

Inside prelude.rs add:

FILE: SimpleCRUD/src/fruits_table/prelude.rs

pub use super::fruits::{
    ActiveModel as FruitsActiveModel, Column as FruitsColumn, Entity as Fruits,
    Model as FruitsModel, PrimaryKey as FruitsPrimaryKey, Relation as FruitsRelation,
};

This code reads the Entity from the generated code and renames it to Fruits to avoid name collisions with other existing Entities. The same goes for the Model, Relation, ActiveModel, Column, etc..

Inside mod.rs , export the modules using:

FILE: SimpleCRUD/src/fruits_table/mod.rs

pub mod prelude;

pub mod fruits;

Add code to perform execution

// Code snippet

#[async_std::main]
async fn main() -> Result<()>{
     // Read the database environment from the `.env` file
+ 	 let env_database_url = include_str!("../.env").trim();
     // Split the env url
+    let split_url: Vec<&str> = env_database_url.split("=").collect();
     // Get item with the format `database_backend://username:password@localhost/database`
+    let database_url = split_url[1];
+
+    let db = Database::connect(database_url).await?;
+
+    let builder = db.get_database_backend();
+    let schema = Schema::new(builder);
+
+	 let create_table_op =  db.execute(builder.build(&schema.create_table_from_entity(Fruits))).await;
+    println!("`CREATE TABLE fruits` {:?}", 
+        match create_table_op {
+    		Ok(_) => "Operation Successful".to_owned(),
+    		Err(e) => format!("Unsuccessful - Error {:?}", e),
+    	 }
+    );
    Ok(())
}

This operation requires reading the database environment, so we read the .env file using include_str!(".env") and store that result as the database_url variable.

The Database::connect(database_url) creates a DatabaseConnection that we will use to connect to the database abd perform operations. Using this connection, the get_database_backend() method retrieves the database backend in use and then build a schema using Schema::new(builder) which in turn is used by the database backed value stored in the builder variable to build the SQL statement using the .build() method.

Finally, we run the SQL query using the .execute() method on DatabaseConnection stored as the db variable. Running the program using cargo run should print:

$ `CREATE TABLE fruits` "Operation Successful"

Running the operation again should print the error:

$ `CREATE TABLE fruits` "Unsuccessful - Error Exec(\"error returned from database: 1050 (42S01): Table 'fruits' already exists\")"

Automatically deriving the code to perform CRUD operations

If the database we want to use already exists, we can automatically generate an Entity, Model and ActiveModel using sea-orm-cli which we installed in the Installation part of the Introduction.

sea-orm-cli will load the database configuration by reading the .env file we created earlier in order to login to the database using the username and password in this file, then it will load the schema which is the database we specified, create the Entities from all the tables in the selected database and automatically generate the relevant code and of the process is successful, create all the code in the folder we will specify.

In the current working directory of the project, execute:

$ sea-orm-cli generate entity -o src/fruits_table

The structure of the current working directory after sea-orm-cli has done its "magical" code generation:

|-- SimpleCRUD/
    |-- Cargo.toml
    |-- Cargo.lock
    |-- src/
        |-- main.rs
+    	|-- fruits_table/ #Model, ActiveModel and Entity code generated by `sea-orm-cli`
+            |-- fruit.rs 
+            |-- mod.rs
+            |-- prelude.rs

Next, import the fruits_table module for use with the project

File: ./SimpleCRUD/src/main.rs

+ mod fruits_table;

#[async_std::main]
async fn main() -> Result<()>{
	...
    Ok(())
}

That's it, we have automatically loaded and created all the tables in our database as Entities using sea-orm-cli.

Next, we perform Insert operations and print results to the console.

Insert Operation

SeaORM insert and read operations are done using the Entity derived from the Model struct using the EntityTrait.

Let's insert a fruit Apple with a unit price per Kg of $2 and an SKU of FM2022AKB40.

Add chrono crate to get the current time from the system time

[package]
name = "simple-crud"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.52"
async-std = { version = "1.10.0", features = ["attributes"] }
sea-orm = { version = "0.5.0", features = [
    "runtime-async-std-rustls",
    "sqlx-mysql",
    "macros",
], default-features = false }
+ chrono = "0.4.19" # Add chrono here

Modify the current sea-orm features to add the feature with-chrono. This activates Date and Time features.

[package]
name = "simple-crud"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.52"
async-std = { version = "1.10.0", features = ["attributes"] }
chrono = "0.4.19"
sea-orm = { version = "0.5.0", features = [
    "runtime-async-std-rustls",
    "sqlx-mysql",
    "macros",
+   "with-chrono", # New feature
], default-features = false }
chrono = "0.4.19" # Add chrono here

Next, call Utc::now() chrono method to get the system time and then import sea_orm::entity::Set to perform convertions of the Rust data types into SQL ready data type ActiveValue

// -- code snippet --
+ use sea_orm::entity::Set;

#[async_std::main]
async fn main() -> Result<()>{
	...

    // Get current system time
+   let now = chrono::offset::Utc::now();

    // Convert system time to `NaiveDateTime` since SeaORM `DateTime` expects this;
+   let naive_system_time = now.naive_utc();
    
+   let fruit_01 = FruitsActiveModel {
+       name: Set("Apple".to_owned()),
+       datetime_utc: Set(naive_system_time),
+       unit_price: Set(2),
+       sku: Set("FM2022AKB40".to_owned()),
+       ..Default::default()
+   };
+   let fruit_insert_operation = Fruits::insert(fruit_01).exec(&db).await;
    
+   println!("INSERTED ONE: {:?}", fruit_insert_operation?);
    
    Ok(())
}

Since an Entity implements EntityTrait, the insert method is availabe. executing Fruits::insert(fruit_01) will perform the operation on the database using exec(&db).await. Here, the insert operation inserts only one row into the specified database;

Running the program using cargo run should print

$ INSERTED ONE: InsertResult { last_insert_id: 1 }

Let's insert more than one row at a time using the Fruits::insert_many() method.

// -- code snippet --
+ use chrono::offset::Utc;
#[async_std::main]
async fn main() -> Result<()>{
	...
    
+   let fruit_02 = FruitsActiveModel {
+       name: Set("Banana".to_owned()),
+       datetime_utc: Set(Utc::now().naive_utc()),
+       unit_price: Set(2),
+       sku: Set("FM2022AKB41".to_owned()),
+       ..Default::default()
+   };
    
+   let fruit_03 = FruitsActiveModel {
+       name: Set("Pineapple".to_owned()),
+       datetime_utc: Set(Utc::now().naive_utc()),
+       unit_price: Set(8),
+       sku: Set("FM2022AKB42".to_owned()),
+       ..Default::default()
+   };
    
+   let fruit_04 = FruitsActiveModel {
+       name: Set("Mango".to_owned()),
+       datetime_utc: Set(Utc::now().naive_utc()),
+       unit_price: Set(6),
+       sku: Set("FM2022AKB43".to_owned()),
+       ..Default::default()
+   };
+   let fruit_insert_operation = Fruits::insert_many(vec![fruit_02, fruit_03, fruit_04]).exec(&db).await;
    
+   println!("INSERTED MANY: {:?}", fruit_insert_operation?);
    
    Ok(())
}

Running the program with cargo run prints

$ INSERTED MANY: InsertResult { last_insert_id: 3 }

Next up is reading one value or many values from a table.

Read Operation

SeaORM can perform read operations through the Entity::find() method.

Find all rows using in a table

The .all() method in Entity is used to fetch all rows in a table.

//-- snippet --

#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

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

+   let fruits_table_rows = Fruits::find().all(&db).await;
+   println!("{:?}", fruits_table_rows?);

    Ok(())
}

To fetch all the rows inside a table, in this case Fruits, call the .all() method on Fruits::find()

This should print all the rows in the table fruits to the console as an array of Models.

$ [Model { fruit_id: 1, name: "Apple", datetime_utc: 2022-01-22T10:36:39, unit_price: 2, sku: "FM2022AKB40" }, Model { fruit_id: 2, name: "Banana", datetime_utc: 2022-01-22T10:36:39, unit_price: 2, sku: "FM2022AKB41" }, Model { fruit_id: 3, name: "Pineapple", datetime_utc: 2022-01-22T10:36:39, unit_price: 8, sku: "FM2022AKB42" }, Model { fruit_id: 4, name: "Mango", datetime_utc: 2022-01-22T10:36:39, unit_price: 6, sku: "FM2022AKB43" }]

Find one row by the primary key

Call the .find_by_id(primary_key) on Fruits entity (table).

//-- snippet --

#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

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

+   let fruits_by_id = Fruits::find_by_id(2).one(&db).await;
+   println!("{:?}", fruits_by_id?);

    Ok(())
}

The .one() method is used to retrieve one Model that matches the query instead of a Vec<Model> like the .all() method. .one() method returns an Option<Model> where Some(Model) is returned if the Model exists or a None is returned if a Model doesn't exist.

Running the program prints

$ Some(Model { fruit_id: 2, name: "Banana", datetime_utc: 2022-01-22T10:36:39, unit_price: 2, sku: "FM2022AKB41" })

Find and Filter a Row by Column Name

Calling filter() method on Entity::find() returns a Model containing the matching row.

//-- snippet --

#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

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

+   let find_pineapple = Fruits::find()
+    	.filter(FruitsColumn::Name.contains("pineapple"))
+    	.one(&db)
+    	.await;
+   println!("{:?}", find_pineapple?);

    Ok(())
}

The FruitsColumn::Name is a Column that was autoderived by SeaORM from the Model struct fields, which we imported and renamed using use super::fruits::Column as FruitsColumn in the previous section. .contains() method on FruitsColumn allows filtering of the Modelwith Pineapple as it's name. Note that this is case insensitive so even calling .contains(piNeApPle) will yield the same results.

Running the program prints:

$ Some(Model { fruit_id: 3, name: "Pineapple", datetime_utc: 2022-01-22T10:36:39, unit_price: 8, sku: "FM2022AKB42" })

Update Operation

To perform an update in SeaORM, first, fetch the row to perform the operation using Model convert it into an ActiveModel by calling the into() methof on the Model , perform the operation on the field on the ActiveModel and then and then call the .update() method on the ActiveModel. The executed result returns the Model that was updated if successful.

//-- snippet --

+ use sea_orm::sea_query::{Expr, Value}; // Types necessary to perform updates and conversions between types

#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

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

   let find_pineapple = Fruits::find()
    	.filter(FruitsColumn::Name.contains("pineapple"))
    	.one(&db)
    	.await?;
-  println!("{:?}", find_pineapple?);
+  println!("{:?}", find_pineapple.as_ref()); // Reference the `Model` instead of owning it
    
   // Update the `pineapple` column with a new unit price
+  if let Some(pineapple_model) = find_pineapple {
+      let mut pineapple_active_model: FruitsActiveModel = pineapple_model.into();
+      pineapple_active_model.unit_price = Set(10);

+      let updated_pineapple_model: FruitsModel =
+          pineapple_active_model.update(&db).await?;

+      println!("UPDATED PRICE: {:?}", updated_pineapple_model);
+  } else {
+      println!("`Pineapple` column not found");
+  }

    Ok(())
}

Running the program returns

$ UPDATED PRICE: Model { fruit_id: 3, name: "Pineapple", datetime_utc: 2022-01-22T13:35:27, unit_price: 10, sku: "FM2022AKB42" }

Delete Operation

To perform a delete operation in SeaORM, first, fetch the row to perform the operation using Model convert it into an ActiveModel by calling the into() methof on the Model , perform the operation on the field on the ActiveModel and then and then call the .delete() method on the ActiveModel or use Fruit::delete(). The executed result returns the Model that was updated if successful.

//-- snippet --

+ use sea_orm::sea_query::{Expr, Value}; // Types necessary to perform updates and conversions between types

#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

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

   // Delete the `mango` row

+  let find_mango = Fruits::find()
+       .filter(FruitsColumn::Name.contains("mango"))
+       .one(&db)
+       .await;
    
+   if let Some(mango_model) = find_mango? {
+       println!(
+           "DELETED MANGO: {:?}",
+           mango_model.delete(&db).await?
+       );
+   } else {
+       println!("`Mango` row not found");
+   }

    Ok(())
}

Running the program returns

$ DELETED MANGO: DeleteResult { rows_affected: 1 }

Multi-table relationships

This section shows how to use SeaORM to perform operations between the Fruits table and a new suppliers table.

Creating relationships between tables can be verbose when doing so using code. Luckily, SeaORM makes this easy. Define a table called suppliers in the fruit_markets database by taking the following steps:

  1. Login to mysql database using username and password created in the installation part of this tutorial in the previous section and switch to fruit_markets database.

    # Execute 
    use fruit_markets;
    
  2. Create a table suppliers that references the primary key fruit_id of table fruits. This will show the type of fruit the supplier supplies to the fruit markets.

    CREATE TABLE suppliers(
        supplier_id INT NOT NULL AUTO_INCREMENT,
        supplier_name VARCHAR(255) NOT NULL,
        fruit_id  INT NOT NULL,  
        PRIMARY KEY (supplier_id),
        CONSTRAINT fk_fruits
        FOREIGN KEY (fruit_id) 
        REFERENCES fruits(fruit_id)
            ON UPDATE CASCADE
            ON DELETE CASCADE
    ) ENGINE=INNODB;
    
  3. Use sea-orm-cli to generate the Entity, Model, Relationship and ActiveModel.

    $ sea-orm-cli generate entity -o src/suppliers_table -t suppliers
    

    A new directory suppliers_table is created in the src directory containing serveral files with code generated by sea-orm-cli.

  4. Modify the src/suppliers_table/prelude.rs file to export memorable names of the Entity, ActiveModel etc

    - pub use super::suppliers::Entity as Suppliers;
    
    + pub use super::suppliers::{
    +     ActiveModel as SuppliersActiveModel, Column as SuppliersColumn, Entity as Suppliers,
    +     Model as SuppliersModel, PrimaryKey as SuppliersPrimaryKey, Relation as SuppliersRelation,
    + };
    
    
  5. The src/suppliers_table/suppliers.rs contains errors indicating the super::fruits cannot be found in supper. This means the module is not exported properly. Fix this by importing the module:

    //! SeaORM Entity. Generated by sea-orm-codegen 0.5.0
    
    use sea_orm::entity::prelude::*;
    
    #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
    #[sea_orm(table_name = "suppliers")]
    pub struct Model {
        #[sea_orm(primary_key)]
        pub supplier_id: i32,
        pub supplier_name: String,
        pub fruit_id: i32,
    }
    
    #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
    pub enum Relation {
        #[sea_orm(
    -       belongs_to = "super::fruits::Entity",
    +		belongs_to = "crate::Fruits",
            from = "Column::FruitId",
    -       to = "super::fruits::Column::FruitId",
    +       to = "crate::FruitsColumn::FruitId",
            on_update = "Cascade",
            on_delete = "Cascade"
        )]
        Fruits,
    }
    
    - impl Related<super::fruits::Entity> for Entity {
    + impl Related<crate::Fruits> for Entity {
        ...
       }
    
    impl ActiveModelBehavior for ActiveModel {}
    

    sea-orm-cli automatically generates code to bind the suppliers table Model to the primary key of the fruits table using belongs_to = "crate::Fruits", to = "crate::FruitsColumn::FruitId" and impl Related<crate::Fruits> for Entity. This corresponds to the SQL query part

     CONSTRAINT fk_fruits
        FOREIGN KEY (fruit_id) 
        REFERENCES fruits(fruit_id)
            ON UPDATE CASCADE
            ON DELETE CASCADE
    
  6. Import the module to the src/main.rs file

      mod fruits_table;
      use fruits_table::prelude::*;
    + mod suppliers_table;
    + use suppliers_table::prelude::*;
    
    // -- code snippet --
    
    // Convert this main function into async function
    #[async_std::main]
    async fn main() -> Result<()> {
        // -- code snippet --
    }
    

Next, INSERT and SELECT SQL queries on tables with foreign key using SeaORM.

Inserting Values into a Table with a Foreign Key

Insert many suppliers in the supplies table

// -- code snippet --

// Convert this main function into async function
#[async_std::main]
async fn main() -> Result<()> {
    // -- code snippet --
    
+   let supplier_01 = SuppliersActiveModel {
+       supplier_name: Set("John Doe".to_owned()),
+       fruit_id: Set(1_i32),
+       ..Default::default()
+   };

+   let supplier_02 = SuppliersActiveModel {
+       supplier_name: Set("Jane Doe".to_owned()),
+       fruit_id: Set(2_i32),
+       ..Default::default()
+   };

+   let supplier_03 = SuppliersActiveModel {
+       supplier_name: Set("Junior Doe".to_owned()),
+       fruit_id: Set(3_i32),
+       ..Default::default()
+   };

+   let supplier_insert_operation =
+       Suppliers::insert_many(vec![supplier_01, supplier_02, supplier_03])
+           .exec(&db)
+           .await;

+   println!("INSERTED MANY: {:?}", supplier_insert_operation?);

    
 	Ok(())   
}

Executing the program returns

$ INSERTED MANY: InsertResult { last_insert_id: 1 }

SELECTing related tables

SeaORM makes it easy to fetch a table and it's related table referenced by its primary key using the Entity::find().find_with_related(Other_Entity).all(DatabaseConnection) chain of methods.

// --- Code Snippet ---
#[async_std::main]
async fn main() -> Result<()> {
    let env_database_url = include_str!("../.env").trim();
    let split_url: Vec<&str> = env_database_url.split("=").collect();
    let database_url = split_url[1];

    let db = Database::connect(database_url).await?;
    
    // -- Code snippet --
    

    let supplier_insert_operation =
        Suppliers::insert_many(vec![supplier_01, supplier_02, supplier_03])
            .exec(&db)
            .await;

    println!("INSERTED MANY: {:?}", supplier_insert_operation?);
    
+   let who_supplies = Suppliers::find().find_with_related(Fruits).all(&db).await?;
+   dbg!(&who_supplies);
    
    Ok(())
    
}

The operation returns a Vec which contains a tuple (Model, Vec<Model>) which is Vec<(Model, Vec<Model>)>. This means that the first Model , tuple.0 is the Model that has relationships with the other Models in the tuple.1 index which is Vec<Model> .

Running the program, prints:

$
[
    (
        Model {
            supplier_id: 1,
            supplier_name: "John Doe",
            fruit_id: 1,
        },
        [
            Model {
                fruit_id: 1,
                name: "Apple",
                datetime_utc: 2022-01-26T09:16:43,
                unit_price: 2,
                sku: "FM2022AKB40",
            },
        ],
    ),
    (
        Model {
            supplier_id: 2,
            supplier_name: "Jane Doe",
            fruit_id: 2,
        },
        [
            Model {
                fruit_id: 2,
                name: "Banana",
                datetime_utc: 2022-01-26T09:16:43,
                unit_price: 2,
                sku: "FM2022AKB41",
            },
        ],
    ),
    (
        Model {
            supplier_id: 3,
            supplier_name: "Junior Doe",
            fruit_id: 3,
        },
        [
            Model {
                fruit_id: 3,
                name: "Pineapple",
                datetime_utc: 2022-01-26T09:16:43,
                unit_price: 10,
                sku: "FM2022AKB42",
            },
        ],
    ),
]

Thats SeaORM in action. A beginner friendly ORM, one codebase for MySQL, SQLite, MariaDB and PostgreSQL. What else could you ask for :)