Github: Levosia.
Imagine you’re building an application in Rust and need to talk to the database normally you’d use sqlx and it would look like this:
#[derive(Debug, sqlx::FromRow)]
struct User {
id: i32,
name: String,
email: String,
}
async fn create_user(pool: &PgPool, name: &str, email: &str) -> Result<User, sqlx::Error> {
let user = sqlx::query_as::<_, User>(
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
)
.bind(name)
.bind(email)
.fetch_one(pool)
.await?;
Ok(user)
}
Works great. Well.. now I want to add a delete method
sync fn delete_user(pool: &sqlx::PgPool, user_id: i32) -> sqlx::Result<()> {
sqlx::query("DELETE FROM users WHERE id = $1")
.bind(user_id)
.execute(pool)
.await?;
Ok(())
}
… and I want to update the email
async fn update_user_email(pool: &sqlx::PgPool, user_id: i32, new_email: &str) -> sqlx::Result<()> {
sqlx::query("UPDATE users SET email = $1 WHERE id = $2")
.bind(new_email)
.bind(user_id)
.execute(pool)
.await?;
Ok(())
}
In my experience writing methods like this becomes repetitive and anoying. I like Rust and I was bored so I made Levosia. This is a light query and method builder for sqlx, currently only supporting postgres. It leverages proc macros to build methods at compile time.
Now your code looks like this:
#[levosia]
#[derive(Debug, sqlx::FromRow)]
struct User {
id: Autogenerated<i32>,
name: String,
email: String,
}
let user = User::create(String::from("Hello"), String::from("world")).await.unwrap();
user.update_email(String::from("Goodbye")).await.unwrap();
user.delete().await.unwrap();
You can make relations like this:
#[leviosa]
#[derive(Debug, FromRow, Clone)]
struct User {
id: AutoGenerated<i32>,
name: String,
created_at: AutoGenerated<DateTime<Utc>>,
}
#[leviosa]
#[derive(Debug, FromRow, Clone)]
struct Post {
id: AutoGenerated<i32>,
my_data: String,
user: Relation<User>, // Custom type that the macro uses to build lazy loading methods
}
// Notice how the method ignores Autogenerated fields.
// If you want to explicitly set null use an Option in your struct.
let user = User::create(&db, String::from("bob"))
.await
.expect("Could not create");
let relation = Relation::new(user.id.0);
let post = Post::create(&db, String::from("My post data"), relation)
.await
.expect("Could not create");
// lazy load realation
let post_user = post
.clone() // needs to take ownership of self (for now I don't like this)
.load_user(&db) // method automatically added to Post in compile time.
.await
.expect("Failed to execute");
// there is no back relations.. however you can use the find query builder.
// WARNING inputs not santized (yet)
let posts = Posts::find()
.select(&format!("user = {}", post_user.id.0))
.execute(&db)
.await
.expect("Failed where Clause");
I started this project wanting to learn more about procedural macros and build somthing cool. This is a proof of concept not a production ready tool. It’s missing many features, the query bulders do not sanitize input so please be carful if you want to experiment with it.