Usual is a simple wrapper for querying, and deserializing SQL queries in a (relatively) type safe way. Queries are still written in plain SQL, so you, the developer get all of the power (and responsibility) of not having a DSL.
Usual revolves around models. Models let you query the data you want, and only the data you want. Simply add a derive
for UsualModel
.
use usual::{base::Model, base::TryGetRow, query, UsualModel};
derive(UsualModel)
struct Post {
id: i64,
title: String,
content: String,
}
This gives you access to deserialize like a boss (this example uses tokio-postgres):
let _ = client
.execute(
query!("INSERT INTO posts (title, content) VALUES ($1, $2)").as_str(),
&[
&format!("title {}", Utc::now().timestamp_millis()),
&"this is some content",
],
)
.await?;
let rows = client
.query(query!("SELECT {Post} FROM posts").as_str(), &[])
.await?
.iter()
.map(Post::from_row)
.collect::<Vec<_>>();
The only special, usual-specific, language here is {Post}
. This means "all of the fields in the Post
model."
Often, you don't want to query every field on a table, we have that too with the partial
macro.
let partial_rows = client
.query(
query!("SELECT {Post::title,created_at} FROM posts").as_str(),
&[],
)
.await?
.iter()
.map(partial!(Post, title as String, created_at as Time))
.collect::<Vec<_>>();
let post = partial_rows.get(0).unwrap();
// This is fine, because we grabbed the title row
println!("title: {}", post.title);
// This is a compile-time error, because we haven't fetched that row from the table.
println!("content: {}", post.content);
The syntax is simple, it's just ModelName::field,field,field
.
Aliasing is supported via a query of the form:
query!("SELECT {TestModel::some_string as t} FROM test_model as t")
This query selects a field from the test_model
table, which we've aliased as t
in the query.
Fetching from multiple tables is also possible, simply add more {}
:
query!("SELECT {TestModel as t}, {TestModel2 as t2} FROM test_model as t JOIN test_model as t2 on t.id = t2.id")
This will let you do a single query and hydrate multiple types of objects from the resulting rows.
Including values not stored in SQL can be achieved by using the #[unusual]
attribute. In order to be unusual, a field must implement Default
, as when the struct is created this is what will be called for that field.
use usual::{base::Model, base::TryGetRow, query, UsualModel};
struct SomethingElse {}
derive(UsualModel)
struct Post {
id: i64,
title: String,
content: String,
#[unusual]
non_sql: Option<SomethingElse>
}