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
-
Install SeaORM-Cli that will help in reading a database schema and generating the relevant
Entity,ModelandRelationof every table in our selected database (schema).$ cargo install sea-orm-cli -
Create a new Rust Cargo project
$ cargo new SimpleCrud --name simple-crud -
Switch to the new cargo project
$ cd simple-crud -
Add SeaORM as a dependency in
Cargo.tomlfileIf you have
cargo editinstalled, run$ cargo add sea-orm --no-default-features --features "runtime-async-std-rustls sqlx-mysql macros"or if you don't have
cargo editinstalled, you can install it by running$ cargo install cargo-edit -
Add the async runtime
$ cargo add anyhow $ cargo add async-std --features attributesYou can also add them manually in the
Cargo.tomlfilesea-orm = { version = "0.5", features = [ "runtime-async-std-rustls", "sqlx-mysql", "macros" ], default-features = false} anyhow = "1" async-std = "1" -
Make sure that your database server is running, then login and create a database called
fruit_markets.CREATE DATABASE fruit_markets; -
Create a new user in the database called
webmasterand with a passwordmaster_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:
-
Login to mysql database using username and password created in the
installationpart of this tutorial in the previous section and switch to fruit_markets database.# Execute use fruit_markets; -
Create a table
suppliersthat references the primary keyfruit_idof tablefruits. 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; -
Use
sea-orm-clito generate theEntity,Model,RelationshipandActiveModel.$ sea-orm-cli generate entity -o src/suppliers_table -t suppliersA new directory
suppliers_tableis created in thesrcdirectory containing serveral files with code generated bysea-orm-cli. -
Modify the
src/suppliers_table/prelude.rsfile to export memorable names of theEntity, ActiveModeletc- 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, + }; -
The
src/suppliers_table/suppliers.rscontains errors indicating thesuper::fruitscannot be found insupper. 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-cliautomatically generates code to bind thesupplierstableModelto the primary key of thefruitstable usingbelongs_to = "crate::Fruits",to = "crate::FruitsColumn::FruitId"andimpl Related<crate::Fruits> for Entity. This corresponds to the SQL query partCONSTRAINT fk_fruits FOREIGN KEY (fruit_id) REFERENCES fruits(fruit_id) ON UPDATE CASCADE ON DELETE CASCADE -
Import the module to the
src/main.rsfilemod 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 :)