Atmosphere
Atmosphere is a lightweight SQL framework designed for sustainable, database-reliant systems. It leverages Rust's powerful type and macro systems to derive SQL schemas from your rust struct definitions into an advanced trait system.
It works by leveraging the sqlx
crate and the Rust macro system to allow
you to work easily with relational-database mapped entities, while still
enabling low level usage of the underlying sqlx
concepts.
Sections
Getting Started
To get started with atmosphere, install it and set up your first package. Next, you will want to define your schema.
Installation
Install Atmosphere
The easiest way to get started with atmosphere is to add the current stable release from crates.io as your dependency:
cargo add atmosphere
Define your Schema
To make use of Atmosphere, you must define your schema in a way that Atmosphere
can understand it. To do so, you use Rust structs augmented with the Schema
derive macro and some metadata which tells it how to map it to SQL.
Here is an example of what such a schema might look like if you are storing users and posts in a database.
extern crate atmosphere; extern crate sqlx; use atmosphere::prelude::*; #[derive(Schema)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } #[derive(Schema)] #[table(schema = "public", name = "post")] struct Post { #[sql(pk)] id: i32, #[sql(fk -> User, rename = "author_id")] author: i32, #[sql(unique)] title: String, } fn main() { }
Table properties
Every type you annotate like this corresponds to one table in your Postgres
database. You must set the table and schema name of the entities by setting
the appropriate keys on the #[table]
annotation.
extern crate atmosphere; extern crate sqlx; use atmosphere::prelude::*; #[derive(Schema)] #[table(schema = "public", name = "users")] struct User { #[sql(pk)] id: i32, // ... } fn main() { }
Column properties
Every struct member corresponds to one row of your backing table. Here you can
use the #[sql]
annotation to add metadata.
Queries
When using Atmosphere, you have two options for writing queries. Once you
derive Schema
on your entities, it gives you the ability to use querying
traits that Atmosphere comes with. However, you can at any point reach down
and write your queries in raw SQL, the way you would if you used sqlx
directly.
Using Atmosphere Traits
Given the Schema from the section before, here is some examples that show how Atmosphere creates traits that allow for simple operations on the tables.
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "post")] struct Post { #[sql(pk)] id: i32, #[sql(fk -> User, rename = "author_id")] author: i32, #[sql(unique)] title: String, } async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; let mut user = User { id: 0, name: "demo".to_owned(), email: "some@email.com".to_owned(), }; user.save(&pool).await?; user.delete(&pool).await?; user.create(&pool).await?; assert_eq!( User::read(&pool, &0).await?, User::find_by_email(&pool, &"some@email.com".to_string()).await?.unwrap() ); let mut post = Post { id: 0, author: 0, title: "test".to_owned() }; post.save(&pool).await?; Post::find_by_author(&pool, &0).await?; // Inter-Table Operations Post { id: 1, author: 0, title: "test1".to_owned() } .author(&pool).await?; user.posts(&pool).await?; user.delete_posts(&pool).await?; Ok(()) } fn main() {}
Using raw SQL
As previously explained, it is always possible to reach down and perform raw SQL
queries on an Atmosphere pool, since it is just an alias for an sqlx
one.
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; sqlx::query("DROP TABLE foo;") .execute(&pool).await?; Ok(()) } fn main() {}
Traits
Given that you have derived Schema
for your entities, Atmosphere provides you
with some traits for simple CRUD operations on your database. These make it
very straightforward to do simple operations, but keep in mind that you are
always able to reach down and write raw SQL manually where it makes sense.
Here are the traits that Atmosphere exposes, along with some examples:
Create
The Create
trait allows you to create new rows in your tables. Here is an example
of how to create a user, given that you have derived its Schema
:
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; let mut user = User { id: 0, name: "demo".to_owned(), email: "some@email.com".to_owned(), }; user.create(&pool).await?; Ok(()) } fn main() {}
Read
The Read
trait allows you to read entities from rows in your table. Here is
an example of how to create a user, given that you have derived its Schema
:
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; // fetch all users let users = User::find_all(&pool).await?; // find user by primary key let mut user = User::find(&pool, &0).await?; // refresh user data user.reload(&pool).await?; Ok(()) } fn main() {}
Update
The Update
trait allows you to read entities from rows in your table. Here is
an example of how to create a user, given that you have derived its Schema
:
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; // find user by primary key let mut user = User::find(&pool, &0).await?; user.email = "joe@example.com".into(); // update user data user.update(&pool).await?; Ok(()) } fn main() {}
Delete
The Delete
trait allows you to read entities from rows in your table. Here is
an example of how to create a user, given that you have derived its Schema
:
extern crate atmosphere; extern crate sqlx; extern crate tokio; use atmosphere::prelude::*; #[derive(Schema, Debug, PartialEq)] #[table(schema = "public", name = "user")] struct User { #[sql(pk)] id: i32, name: String, #[sql(unique)] email: String, } async fn test() -> std::result::Result<(), Box<dyn std::error::Error>> { let database = std::env::var("DATABASE_URL").unwrap(); let pool = atmosphere::Pool::connect(&database).await?; // find user by primary key let mut user = User::find(&pool, &0).await?; // delete user data user.delete(&pool).await?; // delete by primary key User::delete_by(&pool, &4).await?; Ok(()) } fn main() {}