| Crates.io | agrum |
| lib.rs | agrum |
| version | 0.2.2 |
| created_at | 2025-06-09 20:28:35.3126+00 |
| updated_at | 2025-06-09 20:28:35.3126+00 |
| description | This is an OMM database layer. |
| homepage | |
| repository | https://github.com/chanmix51/agrum |
| max_upload_size | |
| id | 1706373 |
| size | 88,331 |
Agrum is a crate designed to make the SQL code maintainable and testable while letting developpers to focus on the business value of the queries.
This library is still in early development stage, means it is not production ready.
The ideas behind Agrum are:
A SQL query (mostly SELECT) is a declarative chain of transformations. Projection is done by explicitely stating how output values are mapped. If the code allows to tune the SQL projection it maps how entities are hydrated from SQL queries.
let sql = #"
select
customer_id,
name,
age(datebirth) as age
from customer
where customer_id = *?"#;
let results = client.query(sql, &[customer_id])
The code above is fragile since a change either in the database or the application model would require all the queries to be modified.
let projection = get_projection();
let sql = #"select {projection} from customer where customer_id = *?"#;
The code above delegates the projection management to a get_projection function. It sets in a unique place the projection definition for all queries on the customer table. It is testable. It makes SQL queries easier to write.
Most of the time the source of SQL query data are tables but it can also be views, functions, sub queries, fixed sets (VALUES). In a way, data source is always a SET of data, a table being a persistent SET. Since a query can be used as a data source (being a programmable set), it is possible to chain data transformations by chaining queries exactly the same way it is done in a SQL WITH statement. Agrum aims at leveraging this behavior to use SQL as a mapping layer between data collection (tables) and the Rust code.
As June 2025, Agrum is in early stage. The projection mechanism works but it still needs to be hardened on real world situations. The usability is still poor, it is very verbose as anything needs to be defined by hand, an annotation mechanism would ease a lot that part and is still to be done.
Agrum organizes the database code in 3 layers:
Determine what SQL query you want to issue using your favorite SQL client (not to say psql). Once you know exactly the SQL query you need, put it as a test for the SqlDefinition you will create. This SQL definition will be split in two responsibilities:
SELECT part) that is required to build the Rust structure that will hold the dataWHERE part) that can vary using the same SQL query to represent different data contextsThe same query can be used to hydrate several kinds of Rust structures. The same query for the same structure can be used with different set of conditions.
As an example, we can imagine the list of objects for sale. There are 3 different views of those objects: private view with full details and price, public with less details and small for compact lists. The query remains the same: get the list of objects that are still for sale and on display but the projection of the query changes.
struct WhateverEntityDataBook<'client> {
provider: Provider<'client, WhateverEntity>,
}
impl<'client> WhateverEntityDataBook<'client> {
pub fn new(provider: Provider<'client, WhateverEntity>) -> Self {
Self { provider }
}
pub async fn fetch_all(&self) -> Result<Vec<WhateverEntity>, Box<dyn Error + Sync + Send>> {
self.provider.fetch(WhereCondition::default()).await
}
pub async fn fetch_by_id(
&self,
thing_id: i32,
) -> Result<Option<WhateverEntity>, Box<dyn Error + Sync + Send>> {
let condition = WhereCondition::new("thing_id = $?", params![thing_id]);
let entity = self.provider.fetch(condition).await?.pop();
Ok(entity)
}
}