Agrum
Agrum is a database access layers 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. If you are looking for a mature solution, have a look at Elephantry
What is Agrum?
Relational databases are a very handy way to store data because they handle correctly atomic concurrent transactions. Furthermore the SQL language proposes a vast and useful set of features that would be very tiedous to re-implement in Rust: joining, filtering or sorting data in a performance wise way. Furthermore, being a declarative languge makes SQL a very solid error prone language: once a SQL query is marked 'good to go' it normally won't break nor fail (check your NULLs handling). This means the most you ask the database, the less you have to implement and test in Rust, leting data intensive operations as close to the metal as possible.
The ideas behind Agrum are:
- mitigate the Object-Relational impedance mismatch by using a Projection led mechanism
- let developers write the SQL they want
- have a testable database access layer
- make queries eventually re-usable for different entities
- turn the database into a composable set of data sources
What does working with Agrum look like?
For now, Agrum is still under heavy work and is not production ready. Examples below show the state of the crate as is.
QueryBooks
QueryBooks are responsible of building the queries that will be sent to the database server. The main idea behind query books is to manipulate SQL query templates where the projection and the conditions are dynamically expanded.
It is a way of making the database layer testable: be able to
test what query is sent to the server for each particular business demand.
It uses the WhereCondition internally to ease conditions composition.
With the service layer shown above, the corresponding QueryBook would be:
}
Since pretty much of the SELECT, INSERT, DELETE or UPDATE sql statements used in
development are simple queries they are available as traits. But the QueryBook patern makes
Agrum able to hold complex queries (see below).
Conditions
Conditions designate the where part of SQL queries. It is handled by the
WhereCondition structure that holds the different SQL conditions and their
associated parameters.
let condition = new
.and_where;
// will expand to `stuff_id=$? and (substuff_id in ($?, $?, $?) or is_alone)`
SQL Entities
SQL entities are entities returned by the queries. This means they are tied to a particular projection. The entities associated with the Query Book presented above are the following:
Ideally, the goal would be something as simple as:
Nested SQL entities
Because Postgres used to be an object oriented database, it is possible to nest entities. In Postgres, defining a table is the same as defining a new type hence it is possible to create fields with these new types which are just composite types. For example it is perfectly fine to do this in Postgres'SQL:
select
company.company_id as company_id,
company.name as name,
address as default_address -- <-+ address is the type specified by the join below
from company -- | it will return the composite type
join address -- <-+
on address.address_id = company.default_address_id
where company.company_id = '…'::uuid;
The output is:
company_id │ name │ default_address
──────────────────────────────────────┼───────┼────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
a7b5f2c8-8816-4c40-86bf-64e066a8db7a │ first │ (ffb3ef0e-697d-4fba-bc4f-28317dc44626,"FIRST HQ",a7b5f2c8-8816-4c40-86bf-64e066a8db7a,"3 rue de la marche",57300,Mouzillon-Sur-Moselle,529fb92…
│ │…0-6df7-4637-8f7f-0878ee140a0f)
(1 row)
in other words, select my_table from my_table means: «give me all the records with the
type my_table».
Use of the crate postgres-types allow to map composite types to Rust structs.
This means we can declare aggregates entities and fetch them directly from the
database.
Testing queries
The QueryBook patern makes it easy to test the resulting query (or parts of it).
let query = default.getForbiddenCategories;
assert_eq!
assert_eq!